diff --git a/404.html b/404.html index 0df8b2532..72a47cd7b 100644 --- a/404.html +++ b/404.html @@ -15,8 +15,8 @@ - - + +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

diff --git a/assets/images/modify-admin-appearance-cc0e975f95d5dce4062cdeaf403a37d0.webp b/assets/images/modify-admin-appearance-cc0e975f95d5dce4062cdeaf403a37d0.webp new file mode 100644 index 000000000..6a2d168e0 Binary files /dev/null and b/assets/images/modify-admin-appearance-cc0e975f95d5dce4062cdeaf403a37d0.webp differ diff --git a/assets/images/webp-column-5f5ad6cace4b91fa19882b3060fda7b0.webp b/assets/images/webp-column-5f5ad6cace4b91fa19882b3060fda7b0.webp new file mode 100644 index 000000000..a236cc050 Binary files /dev/null and b/assets/images/webp-column-5f5ad6cace4b91fa19882b3060fda7b0.webp differ diff --git a/assets/js/183053be.3b310b64.js b/assets/js/183053be.addd0425.js similarity index 78% rename from assets/js/183053be.3b310b64.js rename to assets/js/183053be.addd0425.js index 1d70518b3..5872aec1a 100644 --- a/assets/js/183053be.3b310b64.js +++ b/assets/js/183053be.addd0425.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[22648],{67472:s=>{s.exports=JSON.parse('{"label":"images","permalink":"/blog/tags/images","allTagsPath":"/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[22648],{67472:s=>{s.exports=JSON.parse('{"label":"images","permalink":"/blog/tags/images","allTagsPath":"/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/29c85621.f455a4d2.js b/assets/js/29c85621.f455a4d2.js new file mode 100644 index 000000000..cdcdf1efc --- /dev/null +++ b/assets/js/29c85621.f455a4d2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[57044],{68928:s=>{s.exports=JSON.parse('{"label":"classes","permalink":"/blog/tags/classes","allTagsPath":"/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/3b5edcc4.064b5062.js b/assets/js/3b5edcc4.91046e6a.js similarity index 82% rename from assets/js/3b5edcc4.064b5062.js rename to assets/js/3b5edcc4.91046e6a.js index 7e588ec7b..ebe207296 100644 --- a/assets/js/3b5edcc4.064b5062.js +++ b/assets/js/3b5edcc4.91046e6a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[19504],{52912:t=>{t.exports=JSON.parse('{"permalink":"/blog/tags/images","page":1,"postsPerPage":9,"totalPages":1,"totalCount":1,"blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[19504],{52912:t=>{t.exports=JSON.parse('{"permalink":"/blog/tags/images","page":1,"postsPerPage":9,"totalPages":1,"totalCount":2,"blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file diff --git a/assets/js/430a0c78.557b7fd8.js b/assets/js/430a0c78.557b7fd8.js new file mode 100644 index 000000000..91632adc4 --- /dev/null +++ b/assets/js/430a0c78.557b7fd8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[86042],{9748:t=>{t.exports=JSON.parse('{"permalink":"/blog/tags/colors","page":1,"postsPerPage":9,"totalPages":1,"totalCount":1,"blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file diff --git a/assets/js/52c25d1c.ed0dce00.js b/assets/js/52c25d1c.dd510dee.js similarity index 53% rename from assets/js/52c25d1c.ed0dce00.js rename to assets/js/52c25d1c.dd510dee.js index f7b57d49d..56712abbd 100644 --- a/assets/js/52c25d1c.ed0dce00.js +++ b/assets/js/52c25d1c.dd510dee.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[54572],{29890:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>r,toc:()=>u});var a=n(17624),o=n(4552);const i={title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:new Date("2024-02-01T00:00:00.000Z"),tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},l=void 0,r={permalink:"/blog/making-your-project-multilingual",source:"@site/blog/2024-02-01-making-your-project-multilingual.md",title:"Making your project multilingual",description:"Examples of using I18n in a project",date:"2024-02-01T00:00:00.000Z",formattedDate:"February 1, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"i18n",permalink:"/blog/tags/i-18-n"},{label:"multilingual",permalink:"/blog/tags/multilingual"}],readingTime:5.75,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:"2024-02-01T00:00:00.000Z",tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"How to use the Wrapper as a standalone component",permalink:"/blog/wrapper-as-a-standalone-component"}},s={authorsImageUrls:[void 0]},u=[];function c(t){const e={p:"p",...(0,o.M)(),...t.components};return(0,a.jsx)(e.p,{children:"Tools like Google Translate can automatically translate websites with reasonable quality. However, users will have a much better experience if you add support for multiple languages in your project and manage the translations yourself."})}function p(t={}){const{wrapper:e}={...(0,o.M)(),...t.components};return e?(0,a.jsx)(e,{...t,children:(0,a.jsx)(c,{...t})}):c(t)}},4552:(t,e,n)=>{n.d(e,{I:()=>r,M:()=>l});var a=n(11504);const o={},i=a.createContext(o);function l(t){const e=a.useContext(i);return a.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function r(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:l(t.components),a.createElement(i.Provider,{value:e},t.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[54572],{29890:(t,e,a)=>{a.r(e),a.d(e,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>r,toc:()=>u});var n=a(17624),o=a(4552);const i={title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:new Date("2024-02-01T00:00:00.000Z"),tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},l=void 0,r={permalink:"/blog/making-your-project-multilingual",source:"@site/blog/2024-02-01-making-your-project-multilingual.md",title:"Making your project multilingual",description:"Examples of using I18n in a project",date:"2024-02-01T00:00:00.000Z",formattedDate:"February 1, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"i18n",permalink:"/blog/tags/i-18-n"},{label:"multilingual",permalink:"/blog/tags/multilingual"}],readingTime:5.75,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:"2024-02-01T00:00:00.000Z",tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Tips & useful features",permalink:"/blog/tips-useful-features"},nextItem:{title:"How to use the Wrapper as a standalone component",permalink:"/blog/wrapper-as-a-standalone-component"}},s={authorsImageUrls:[void 0]},u=[];function c(t){const e={p:"p",...(0,o.M)(),...t.components};return(0,n.jsx)(e.p,{children:"Tools like Google Translate can automatically translate websites with reasonable quality. However, users will have a much better experience if you add support for multiple languages in your project and manage the translations yourself."})}function p(t={}){const{wrapper:e}={...(0,o.M)(),...t.components};return e?(0,n.jsx)(e,{...t,children:(0,n.jsx)(c,{...t})}):c(t)}},4552:(t,e,a)=>{a.d(e,{I:()=>r,M:()=>l});var n=a(11504);const o={},i=n.createContext(o);function l(t){const e=n.useContext(i);return n.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function r(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:l(t.components),n.createElement(i.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/56f8d3dd.7827c79d.js b/assets/js/56f8d3dd.7827c79d.js new file mode 100644 index 000000000..f27b2fdff --- /dev/null +++ b/assets/js/56f8d3dd.7827c79d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[39624],{84300:(e,i,n)=>{n.r(i),n.d(i,{assets:()=>a,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>o,toc:()=>c});var t=n(17624),r=n(4552);const l={id:"airtable",title:"Airtable"},s=void 0,o={id:"integrations/airtable",title:"Airtable",description:"Airtable is an platform that makes it easy to build powerful, custom applications.",source:"@site/forms/integrations/airtable.md",sourceDirName:"integrations",slug:"/integrations/airtable",permalink:"/forms/integrations/airtable",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"airtable",title:"Airtable"},sidebar:"forms",previous:{title:"ActiveCampaign",permalink:"/forms/integrations/active-campaign"},next:{title:"Clearbit",permalink:"/forms/integrations/clearbit"}},a={},c=[{value:"Website",id:"website",level:3},{value:"API Version",id:"api-version",level:3},{value:"API Documentation",id:"api-documentation",level:3},{value:"Integration type",id:"integration-type",level:3},{value:"Supported fields:",id:"supported-fields",level:3}];function d(e){const i={a:"a",admonition:"admonition",h3:"h3",li:"li",p:"p",ul:"ul",...(0,r.M)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(i.p,{children:"Airtable is an platform that makes it easy to build powerful, custom applications."}),"\n",(0,t.jsx)(i.h3,{id:"website",children:"Website"}),"\n",(0,t.jsxs)(i.ul,{children:["\n",(0,t.jsx)(i.li,{children:(0,t.jsx)(i.a,{href:"https://airtable.com/",children:"Visit website"})}),"\n"]}),"\n",(0,t.jsx)(i.h3,{id:"api-version",children:"API Version"}),"\n",(0,t.jsxs)(i.ul,{children:["\n",(0,t.jsx)(i.li,{children:"V0 - beta"}),"\n"]}),"\n",(0,t.jsx)(i.h3,{id:"api-documentation",children:"API Documentation"}),"\n",(0,t.jsxs)(i.ul,{children:["\n",(0,t.jsx)(i.li,{children:(0,t.jsx)(i.a,{href:"https://airtable.com/developers/web/api/introduction",children:"Documentation"})}),"\n"]}),"\n",(0,t.jsx)(i.h3,{id:"integration-type",children:"Integration type"}),"\n",(0,t.jsxs)(i.ul,{children:["\n",(0,t.jsx)(i.li,{children:"Form builder provided by the service."}),"\n"]}),"\n",(0,t.jsx)(i.h3,{id:"supported-fields",children:"Supported fields:"}),"\n",(0,t.jsxs)(i.ul,{children:["\n",(0,t.jsx)(i.li,{children:"Input"}),"\n",(0,t.jsx)(i.li,{children:"Email"}),"\n",(0,t.jsx)(i.li,{children:"Url"}),"\n",(0,t.jsx)(i.li,{children:"Number"}),"\n",(0,t.jsx)(i.li,{children:"Date"}),"\n",(0,t.jsx)(i.li,{children:"Phone number"}),"\n",(0,t.jsx)(i.li,{children:"Textarea"}),"\n",(0,t.jsx)(i.li,{children:"Single Select"}),"\n",(0,t.jsx)(i.li,{children:"Multiple Choices"}),"\n",(0,t.jsx)(i.li,{children:"Checkbox"}),"\n",(0,t.jsx)(i.li,{children:"Currency"}),"\n",(0,t.jsx)(i.li,{children:"MultipleRecordLinks"}),"\n"]}),"\n",(0,t.jsx)(i.admonition,{type:"caution",children:(0,t.jsx)(i.p,{children:"The Airtable MultipleRecordLinks field relies on the Airtable API to create records in a linked table.\nTo optimize the performance of forms, you cannot change/preview these records in the backend. They are pulled and displayed only in the frontend."})})]})}function h(e={}){const{wrapper:i}={...(0,r.M)(),...e.components};return i?(0,t.jsx)(i,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},4552:(e,i,n)=>{n.d(i,{I:()=>o,M:()=>s});var t=n(11504);const r={},l=t.createContext(r);function s(e){const i=t.useContext(l);return t.useMemo((function(){return"function"==typeof e?e(i):{...i,...e}}),[i,e])}function o(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(l.Provider,{value:i},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/56f8d3dd.ae3970f6.js b/assets/js/56f8d3dd.ae3970f6.js deleted file mode 100644 index 7bed6948d..000000000 --- a/assets/js/56f8d3dd.ae3970f6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[39624],{84300:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>l,metadata:()=>a,toc:()=>c});var n=t(17624),r=t(4552);const l={id:"airtable",title:"Airtable"},s=void 0,a={id:"integrations/airtable",title:"Airtable",description:"Airtable is an platform that makes it easy to build powerful, custom applications.",source:"@site/forms/integrations/airtable.md",sourceDirName:"integrations",slug:"/integrations/airtable",permalink:"/forms/integrations/airtable",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"airtable",title:"Airtable"},sidebar:"forms",previous:{title:"ActiveCampaign",permalink:"/forms/integrations/active-campaign"},next:{title:"Clearbit",permalink:"/forms/integrations/clearbit"}},o={},c=[{value:"Website",id:"website",level:3},{value:"API Version",id:"api-version",level:3},{value:"API Documentation",id:"api-documentation",level:3},{value:"Integration type",id:"integration-type",level:3},{value:"Supported fields:",id:"supported-fields",level:3}];function d(e){const i={a:"a",h3:"h3",li:"li",p:"p",ul:"ul",...(0,r.M)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(i.p,{children:"Airtable is an platform that makes it easy to build powerful, custom applications."}),"\n",(0,n.jsx)(i.h3,{id:"website",children:"Website"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"https://airtable.com/",children:"Visit website"})}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"api-version",children:"API Version"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"V0 - beta"}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"api-documentation",children:"API Documentation"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:(0,n.jsx)(i.a,{href:"https://airtable.com/developers/web/api/introduction",children:"Documentation"})}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"integration-type",children:"Integration type"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Form builder provided by the service."}),"\n"]}),"\n",(0,n.jsx)(i.h3,{id:"supported-fields",children:"Supported fields:"}),"\n",(0,n.jsxs)(i.ul,{children:["\n",(0,n.jsx)(i.li,{children:"Input"}),"\n",(0,n.jsx)(i.li,{children:"Email"}),"\n",(0,n.jsx)(i.li,{children:"Url"}),"\n",(0,n.jsx)(i.li,{children:"Number"}),"\n",(0,n.jsx)(i.li,{children:"Date"}),"\n",(0,n.jsx)(i.li,{children:"Phone number"}),"\n",(0,n.jsx)(i.li,{children:"Textarea"}),"\n",(0,n.jsx)(i.li,{children:"Single Select"}),"\n",(0,n.jsx)(i.li,{children:"Multiple Choices"}),"\n",(0,n.jsx)(i.li,{children:"Checkbox"}),"\n"]})]})}function u(e={}){const{wrapper:i}={...(0,r.M)(),...e.components};return i?(0,n.jsx)(i,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},4552:(e,i,t)=>{t.d(i,{I:()=>a,M:()=>s});var n=t(11504);const r={},l=n.createContext(r);function s(e){const i=n.useContext(l);return n.useMemo((function(){return"function"==typeof e?e(i):{...i,...e}}),[i,e])}function a(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),n.createElement(l.Provider,{value:i},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5ba3e239.71daf983.js b/assets/js/5ba3e239.71daf983.js deleted file mode 100644 index 984a35121..000000000 --- a/assets/js/5ba3e239.71daf983.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[30156],{6972:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var i=t(17624),s=t(4552);const o={title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:new Date("2024-02-01T00:00:00.000Z"),tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},r=void 0,a={permalink:"/blog/making-your-project-multilingual",source:"@site/blog/2024-02-01-making-your-project-multilingual.md",title:"Making your project multilingual",description:"Examples of using I18n in a project",date:"2024-02-01T00:00:00.000Z",formattedDate:"February 1, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"i18n",permalink:"/blog/tags/i-18-n"},{label:"multilingual",permalink:"/blog/tags/multilingual"}],readingTime:5.75,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:"2024-02-01T00:00:00.000Z",tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"How to use the Wrapper as a standalone component",permalink:"/blog/wrapper-as-a-standalone-component"}},l={authorsImageUrls:[void 0]},c=[{value:"Making strings translatable in PHP",id:"making-strings-translatable-in-php",level:2},{value:"Making strings translatable in JS",id:"making-strings-translatable-in-js",level:2},{value:"The I18n class",id:"the-i18n-class",level:2},{value:"Generating .pot file",id:"generating-pot-file",level:2},{value:"Translating with Poedit",id:"translating-with-poedit",level:2},{value:"JS translations",id:"js-translations",level:2},{value:"Enabling languages and content translation",id:"enabling-languages-and-content-translation",level:2},{value:"Additional resources",id:"additional-resources",level:2}];function d(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.M)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.p,{children:"Tools like Google Translate can automatically translate websites with reasonable quality. However, users will have a much better experience if you add support for multiple languages in your project and manage the translations yourself."}),"\n",(0,i.jsx)(n.h2,{id:"making-strings-translatable-in-php",children:"Making strings translatable in PHP"}),"\n",(0,i.jsx)(n.p,{children:"A good practice is to use one of the I18n (internationalization) functions for your hardcoded strings, even if your website starts with a single language. This way, you can add multilingual support more easily later."}),"\n",(0,i.jsxs)(n.p,{children:["If you've worked on a multilanguage-capable project, you most likely came across ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," functions. The main difference between the ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," is that ",(0,i.jsx)(n.code,{children:"__()"})," returns the value, while ",(0,i.jsx)(n.code,{children:"_e()"})," echoes it. Both functions take two arguments: the first one is the string to be translated, and the second one is the textdomain that identifies the translation file."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"Textdomain is usually your project name written in kebab-case."}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["While WordPress functions like ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," will definitely do the job, it is much better to use the variants of these functions that also escape the output. These are ",(0,i.jsx)(n.code,{children:"esc_html__()"})," and ",(0,i.jsx)(n.code,{children:"esc_html_e()"}),". There are also a few more functions for I18n you can use, but to keep it simple, we'll just mention these two for now."]}),"\n",(0,i.jsx)(n.p,{children:"Here is an example of using one of these functions:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"\n"})}),"\n",(0,i.jsx)(n.h2,{id:"making-strings-translatable-in-js",children:"Making strings translatable in JS"}),"\n",(0,i.jsxs)(n.p,{children:["To translate the strings in the Block editor or options, you will first have to import the function from the ",(0,i.jsx)(n.code,{children:"@wordpress/i18n"})," library."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-jsx",children:"import { __ } from '@wordpress/i18n';\n"})}),"\n",(0,i.jsx)(n.p,{children:"To output your string, simply use it like this:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-jsx",children:"{__('Icon position', 'project-name')}\n"})}),"\n",(0,i.jsx)(n.p,{children:"Alternative functions you can use are:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_n"})," for singular/plural forms"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_nx"})," for singular/plural forms with ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_x"})," for a translated string with a ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["You can refer to the ",(0,i.jsx)(n.a,{href:"https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/",children:"block editor handbook"})," for more information on these functions."]}),"\n",(0,i.jsx)(n.h2,{id:"the-i18n-class",children:"The I18n class"}),"\n",(0,i.jsx)(n.p,{children:"The easiest way to add I18n support to a project created with Eightshift boilerplate is by using the WP-CLI command:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create i18n\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This command generated a new class inside the ",(0,i.jsx)(n.code,{children:"src/I18n"})," folder. This class instructs WordPress to look for translations in ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," with the textdomain defined as your project name. The next step is generating .po and .mo files that are used for translation."]}),"\n",(0,i.jsx)(n.h2,{id:"generating-pot-file",children:"Generating .pot file"}),"\n",(0,i.jsxs)(n.p,{children:["You can create a ",(0,i.jsx)(n.code,{children:".pot"})," (",(0,i.jsx)(n.em,{children:"Portable object template"}),") file by using WP-CLI. Run the following command in your project root:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp i18n make-pot\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Alternatively, you can use tools like ",(0,i.jsx)(n.a,{href:"https://poedit.net/",children:"Poedit"})," to generate a ",(0,i.jsx)(n.code,{children:".pot"})," file and generate translations from it later."]}),"\n",(0,i.jsx)(n.h2,{id:"translating-with-poedit",children:"Translating with Poedit"}),"\n",(0,i.jsxs)(n.p,{children:["Once you have the ",(0,i.jsx)(n.code,{children:".pot"})," file, you can use Poedit to generate ",(0,i.jsx)(n.code,{children:".po"})," and ",(0,i.jsx)(n.code,{children:".mo"})," files that are used for translating hardcoded strings in your project. When generating the files, you can choose for which locale you're creating the translation for. For example, if you are creating a translation for the German language, your files should be named ",(0,i.jsx)(n.code,{children:"de_DE.po"})," and ",(0,i.jsx)(n.code,{children:"de_DE.mo"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["After generating the files, go to ",(0,i.jsx)(n.strong,{children:"Translation -> Properties"})," and navigate to the ",(0,i.jsx)(n.strong,{children:"Sources Paths"})," tab. Set the ",(0,i.jsx)(n.em,{children:"Base path"})," to the theme folder path. In ",(0,i.jsx)(n.em,{children:"Excluded paths"})," you can add folders like ",(0,i.jsx)(n.code,{children:"node_modules"}),", ",(0,i.jsx)(n.code,{children:"vendor"}),", and ",(0,i.jsx)(n.code,{children:"public"})," to exclude external packages."]}),"\n",(0,i.jsxs)(n.p,{children:["In the ",(0,i.jsx)(n.em,{children:"Sources keywords"})," tab you can set additional functions for use in your project for translations. Commonly used functions are:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_e"})," for translating a string and echoing it"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"__"})," for returning a translated string"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html__"})," for returning a translated string which is escaped in a way it's safe to use within HTML"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html_e"})," for echoing a translated string which is escaped in a way it's safe to use within HTML"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_attr__"})," for returning a translated string which is escaped in a way it's safe to use within an attribute"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html_x"})," for returning a translated string which is escaped in a way it's safe to use within HTML, with a ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_n"})," for returning a translated string in a singular or plural form, based on the supplied number"]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["If you're missing a string in your ",(0,i.jsx)(n.code,{children:".po"})," file be sure to check which function is used for translation for that string, and that the function is added to ",(0,i.jsx)(n.em,{children:"Sources keywords"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["After updating the settings, click on ",(0,i.jsx)(n.em,{children:"Update from source code"})," option to get the updated list of strings to translate."]}),"\n",(0,i.jsxs)(n.p,{children:["The translation process is simple. The left column represents the source text, and the right column the translation. When you have finished translating the strings, copy the ",(0,i.jsx)(n.code,{children:".po"})," and ",(0,i.jsx)(n.code,{children:".mo"})," files to the ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," folder."]}),"\n",(0,i.jsx)(n.h2,{id:"js-translations",children:"JS translations"}),"\n",(0,i.jsx)(n.p,{children:"The process of translating strings in JS has a couple of extra steps."}),"\n",(0,i.jsxs)(n.p,{children:["In order to translate strings in JS (e.g. Block editor strings), you will have to generate translation file. To do this, navigate to your ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," folder and use the following WP-CLI command:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp i18n make-json --no-purge\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This will generate a ",(0,i.jsx)(n.code,{children:".json"})," file for each JS file present. The strings are extracted from ",(0,i.jsx)(n.code,{children:".po"})," files, so you'll already have the translations added. The ",(0,i.jsx)(n.code,{children:"--no-purge"})," flag is used to keep the existing translations in the ",(0,i.jsx)(n.code,{children:".po"})," file."]}),"\n",(0,i.jsxs)(n.p,{children:["The method used for setting the script translations is ",(0,i.jsx)(n.code,{children:"setScriptTranslations()"})," from the ",(0,i.jsx)(n.code,{children:"I18n"})," class."]}),"\n",(0,i.jsxs)(n.p,{children:["The default way this works in Eightshift DevKit is that you need to have a single ",(0,i.jsx)(n.code,{children:".json"})," file with all the JS translations. If needed, you can either modify this method to read from multiple files, or just merge all the ",(0,i.jsx)(n.code,{children:".json"})," files into one."]}),"\n",(0,i.jsxs)(n.p,{children:["If using the default setup (everything in one file) follow this naming structure: ",(0,i.jsx)(n.code,{children:"{textdomain}-{locale}-{handle}.json"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["For example, if your ",(0,i.jsx)(n.em,{children:"textdomain"})," is ",(0,i.jsx)(n.code,{children:"project-name"})," and your locale is ",(0,i.jsx)(n.code,{children:"de_DE"}),", your file should be named ",(0,i.jsx)(n.code,{children:"project-name-de_DE-project-name-block-editor-scripts.json"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"The block-related translations depend on the language the user has set in WP admin."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"enabling-languages-and-content-translation",children:"Enabling languages and content translation"}),"\n",(0,i.jsx)(n.p,{children:"If the website itself needs to support content in multiple languages, a plugin is a good option."}),"\n",(0,i.jsx)(n.p,{children:"The most common multi-language plugins are:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"WPML"})," - one of the most popular plugins on the market. It is a paid plugin, but offers a lot of advanced options."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Polylang"})," - a free plugin (also has a paid ",(0,i.jsx)(n.em,{children:"Pro"})," version)."]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"Explore other options as well, you might find a plugin that is a better fit for your project than WPML or Polylang."}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Most of the translation work will be done through the editor, since you'll need to translate the content on posts and pages."}),"\n",(0,i.jsx)(n.h2,{id:"additional-resources",children:"Additional resources"}),"\n",(0,i.jsxs)(n.p,{children:["Internationalization (",(0,i.jsx)(n.em,{children:"I18n"}),") and Localization (",(0,i.jsx)(n.em,{children:"L10n"}),") are very broad topics, so it's impossible to cover everything in a single blog post."]}),"\n",(0,i.jsx)(n.p,{children:"If you wish to know about the core I18n functionalities, or a bit more about how it is used in the Eightshift DevKit, here are a few resources which you may find interesting:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://codex.wordpress.org/I18n_for_WordPress_Developers",children:"WordPress Codex - I18n for WordPress Developers"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://eightshift.com/docs/basics/tips-tricks/#internationalization-i18n-and-localization-l10n",children:"Eightshift Development kit documentation - Tips & Tricks"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://infinum.com/handbook/wordpress/translations/localization",children:"Infinum WordPress Handbook - Localization"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.M)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},4552:(e,n,t)=>{t.d(n,{I:()=>a,M:()=>r});var i=t(11504);const s={},o=i.createContext(s);function r(e){const n=i.useContext(o);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5ba3e239.e31270b8.js b/assets/js/5ba3e239.e31270b8.js new file mode 100644 index 000000000..cab7f8a4c --- /dev/null +++ b/assets/js/5ba3e239.e31270b8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[30156],{6972:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var i=t(17624),s=t(4552);const o={title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:new Date("2024-02-01T00:00:00.000Z"),tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},r=void 0,a={permalink:"/blog/making-your-project-multilingual",source:"@site/blog/2024-02-01-making-your-project-multilingual.md",title:"Making your project multilingual",description:"Examples of using I18n in a project",date:"2024-02-01T00:00:00.000Z",formattedDate:"February 1, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"i18n",permalink:"/blog/tags/i-18-n"},{label:"multilingual",permalink:"/blog/tags/multilingual"}],readingTime:5.75,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Making your project multilingual",description:"Examples of using I18n in a project",slug:"making-your-project-multilingual",authors:"obradovic",date:"2024-02-01T00:00:00.000Z",tags:["eightshift","boilerplate","i18n","multilingual"],hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Tips & useful features",permalink:"/blog/tips-useful-features"},nextItem:{title:"How to use the Wrapper as a standalone component",permalink:"/blog/wrapper-as-a-standalone-component"}},l={authorsImageUrls:[void 0]},c=[{value:"Making strings translatable in PHP",id:"making-strings-translatable-in-php",level:2},{value:"Making strings translatable in JS",id:"making-strings-translatable-in-js",level:2},{value:"The I18n class",id:"the-i18n-class",level:2},{value:"Generating .pot file",id:"generating-pot-file",level:2},{value:"Translating with Poedit",id:"translating-with-poedit",level:2},{value:"JS translations",id:"js-translations",level:2},{value:"Enabling languages and content translation",id:"enabling-languages-and-content-translation",level:2},{value:"Additional resources",id:"additional-resources",level:2}];function d(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.M)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.p,{children:"Tools like Google Translate can automatically translate websites with reasonable quality. However, users will have a much better experience if you add support for multiple languages in your project and manage the translations yourself."}),"\n",(0,i.jsx)(n.h2,{id:"making-strings-translatable-in-php",children:"Making strings translatable in PHP"}),"\n",(0,i.jsx)(n.p,{children:"A good practice is to use one of the I18n (internationalization) functions for your hardcoded strings, even if your website starts with a single language. This way, you can add multilingual support more easily later."}),"\n",(0,i.jsxs)(n.p,{children:["If you've worked on a multilanguage-capable project, you most likely came across ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," functions. The main difference between the ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," is that ",(0,i.jsx)(n.code,{children:"__()"})," returns the value, while ",(0,i.jsx)(n.code,{children:"_e()"})," echoes it. Both functions take two arguments: the first one is the string to be translated, and the second one is the textdomain that identifies the translation file."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"Textdomain is usually your project name written in kebab-case."}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["While WordPress functions like ",(0,i.jsx)(n.code,{children:"__()"})," and ",(0,i.jsx)(n.code,{children:"_e()"})," will definitely do the job, it is much better to use the variants of these functions that also escape the output. These are ",(0,i.jsx)(n.code,{children:"esc_html__()"})," and ",(0,i.jsx)(n.code,{children:"esc_html_e()"}),". There are also a few more functions for I18n you can use, but to keep it simple, we'll just mention these two for now."]}),"\n",(0,i.jsx)(n.p,{children:"Here is an example of using one of these functions:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"\n"})}),"\n",(0,i.jsx)(n.h2,{id:"making-strings-translatable-in-js",children:"Making strings translatable in JS"}),"\n",(0,i.jsxs)(n.p,{children:["To translate the strings in the Block editor or options, you will first have to import the function from the ",(0,i.jsx)(n.code,{children:"@wordpress/i18n"})," library."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-jsx",children:"import { __ } from '@wordpress/i18n';\n"})}),"\n",(0,i.jsx)(n.p,{children:"To output your string, simply use it like this:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-jsx",children:"{__('Icon position', 'project-name')}\n"})}),"\n",(0,i.jsx)(n.p,{children:"Alternative functions you can use are:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_n"})," for singular/plural forms"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_nx"})," for singular/plural forms with ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_x"})," for a translated string with a ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["You can refer to the ",(0,i.jsx)(n.a,{href:"https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/",children:"block editor handbook"})," for more information on these functions."]}),"\n",(0,i.jsx)(n.h2,{id:"the-i18n-class",children:"The I18n class"}),"\n",(0,i.jsx)(n.p,{children:"The easiest way to add I18n support to a project created with Eightshift boilerplate is by using the WP-CLI command:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create i18n\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This command generated a new class inside the ",(0,i.jsx)(n.code,{children:"src/I18n"})," folder. This class instructs WordPress to look for translations in ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," with the textdomain defined as your project name. The next step is generating .po and .mo files that are used for translation."]}),"\n",(0,i.jsx)(n.h2,{id:"generating-pot-file",children:"Generating .pot file"}),"\n",(0,i.jsxs)(n.p,{children:["You can create a ",(0,i.jsx)(n.code,{children:".pot"})," (",(0,i.jsx)(n.em,{children:"Portable object template"}),") file by using WP-CLI. Run the following command in your project root:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp i18n make-pot\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Alternatively, you can use tools like ",(0,i.jsx)(n.a,{href:"https://poedit.net/",children:"Poedit"})," to generate a ",(0,i.jsx)(n.code,{children:".pot"})," file and generate translations from it later."]}),"\n",(0,i.jsx)(n.h2,{id:"translating-with-poedit",children:"Translating with Poedit"}),"\n",(0,i.jsxs)(n.p,{children:["Once you have the ",(0,i.jsx)(n.code,{children:".pot"})," file, you can use Poedit to generate ",(0,i.jsx)(n.code,{children:".po"})," and ",(0,i.jsx)(n.code,{children:".mo"})," files that are used for translating hardcoded strings in your project. When generating the files, you can choose for which locale you're creating the translation for. For example, if you are creating a translation for the German language, your files should be named ",(0,i.jsx)(n.code,{children:"de_DE.po"})," and ",(0,i.jsx)(n.code,{children:"de_DE.mo"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["After generating the files, go to ",(0,i.jsx)(n.strong,{children:"Translation -> Properties"})," and navigate to the ",(0,i.jsx)(n.strong,{children:"Sources Paths"})," tab. Set the ",(0,i.jsx)(n.em,{children:"Base path"})," to the theme folder path. In ",(0,i.jsx)(n.em,{children:"Excluded paths"})," you can add folders like ",(0,i.jsx)(n.code,{children:"node_modules"}),", ",(0,i.jsx)(n.code,{children:"vendor"}),", and ",(0,i.jsx)(n.code,{children:"public"})," to exclude external packages."]}),"\n",(0,i.jsxs)(n.p,{children:["In the ",(0,i.jsx)(n.em,{children:"Sources keywords"})," tab you can set additional functions for use in your project for translations. Commonly used functions are:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_e"})," for translating a string and echoing it"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"__"})," for returning a translated string"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html__"})," for returning a translated string which is escaped in a way it's safe to use within HTML"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html_e"})," for echoing a translated string which is escaped in a way it's safe to use within HTML"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_attr__"})," for returning a translated string which is escaped in a way it's safe to use within an attribute"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"esc_html_x"})," for returning a translated string which is escaped in a way it's safe to use within HTML, with a ",(0,i.jsx)(n.em,{children:"gettext"})," context"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"_n"})," for returning a translated string in a singular or plural form, based on the supplied number"]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["If you're missing a string in your ",(0,i.jsx)(n.code,{children:".po"})," file be sure to check which function is used for translation for that string, and that the function is added to ",(0,i.jsx)(n.em,{children:"Sources keywords"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["After updating the settings, click on ",(0,i.jsx)(n.em,{children:"Update from source code"})," option to get the updated list of strings to translate."]}),"\n",(0,i.jsxs)(n.p,{children:["The translation process is simple. The left column represents the source text, and the right column the translation. When you have finished translating the strings, copy the ",(0,i.jsx)(n.code,{children:".po"})," and ",(0,i.jsx)(n.code,{children:".mo"})," files to the ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," folder."]}),"\n",(0,i.jsx)(n.h2,{id:"js-translations",children:"JS translations"}),"\n",(0,i.jsx)(n.p,{children:"The process of translating strings in JS has a couple of extra steps."}),"\n",(0,i.jsxs)(n.p,{children:["In order to translate strings in JS (e.g. Block editor strings), you will have to generate translation file. To do this, navigate to your ",(0,i.jsx)(n.code,{children:"src/I18n/languages"})," folder and use the following WP-CLI command:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wp i18n make-json --no-purge\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This will generate a ",(0,i.jsx)(n.code,{children:".json"})," file for each JS file present. The strings are extracted from ",(0,i.jsx)(n.code,{children:".po"})," files, so you'll already have the translations added. The ",(0,i.jsx)(n.code,{children:"--no-purge"})," flag is used to keep the existing translations in the ",(0,i.jsx)(n.code,{children:".po"})," file."]}),"\n",(0,i.jsxs)(n.p,{children:["The method used for setting the script translations is ",(0,i.jsx)(n.code,{children:"setScriptTranslations()"})," from the ",(0,i.jsx)(n.code,{children:"I18n"})," class."]}),"\n",(0,i.jsxs)(n.p,{children:["The default way this works in Eightshift DevKit is that you need to have a single ",(0,i.jsx)(n.code,{children:".json"})," file with all the JS translations. If needed, you can either modify this method to read from multiple files, or just merge all the ",(0,i.jsx)(n.code,{children:".json"})," files into one."]}),"\n",(0,i.jsxs)(n.p,{children:["If using the default setup (everything in one file) follow this naming structure: ",(0,i.jsx)(n.code,{children:"{textdomain}-{locale}-{handle}.json"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["For example, if your ",(0,i.jsx)(n.em,{children:"textdomain"})," is ",(0,i.jsx)(n.code,{children:"project-name"})," and your locale is ",(0,i.jsx)(n.code,{children:"de_DE"}),", your file should be named ",(0,i.jsx)(n.code,{children:"project-name-de_DE-project-name-block-editor-scripts.json"}),"."]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"The block-related translations depend on the language the user has set in WP admin."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"enabling-languages-and-content-translation",children:"Enabling languages and content translation"}),"\n",(0,i.jsx)(n.p,{children:"If the website itself needs to support content in multiple languages, a plugin is a good option."}),"\n",(0,i.jsx)(n.p,{children:"The most common multi-language plugins are:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"WPML"})," - one of the most popular plugins on the market. It is a paid plugin, but offers a lot of advanced options."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Polylang"})," - a free plugin (also has a paid ",(0,i.jsx)(n.em,{children:"Pro"})," version)."]}),"\n"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"Explore other options as well, you might find a plugin that is a better fit for your project than WPML or Polylang."}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Most of the translation work will be done through the editor, since you'll need to translate the content on posts and pages."}),"\n",(0,i.jsx)(n.h2,{id:"additional-resources",children:"Additional resources"}),"\n",(0,i.jsxs)(n.p,{children:["Internationalization (",(0,i.jsx)(n.em,{children:"I18n"}),") and Localization (",(0,i.jsx)(n.em,{children:"L10n"}),") are very broad topics, so it's impossible to cover everything in a single blog post."]}),"\n",(0,i.jsx)(n.p,{children:"If you wish to know about the core I18n functionalities, or a bit more about how it is used in the Eightshift DevKit, here are a few resources which you may find interesting:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://codex.wordpress.org/I18n_for_WordPress_Developers",children:"WordPress Codex - I18n for WordPress Developers"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://eightshift.com/docs/basics/tips-tricks/#internationalization-i18n-and-localization-l10n",children:"Eightshift Development kit documentation - Tips & Tricks"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://infinum.com/handbook/wordpress/translations/localization",children:"Infinum WordPress Handbook - Localization"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.M)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},4552:(e,n,t)=>{t.d(n,{I:()=>a,M:()=>r});var i=t(11504);const s={},o=i.createContext(s);function r(e){const n=i.useContext(o);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5eed5ded.b1cf7d7e.js b/assets/js/5eed5ded.b1cf7d7e.js new file mode 100644 index 000000000..272683c6f --- /dev/null +++ b/assets/js/5eed5ded.b1cf7d7e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[72924],{53692:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>n,contentTitle:()=>r,default:()=>p,frontMatter:()=>a,metadata:()=>l,toc:()=>u});var i=s(17624),o=s(4552);const a={title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",slug:"tips-useful-features",authors:"obradovic",date:new Date("2024-02-29T00:00:00.000Z"),tags:["eightshift","boilerplate","blocks","plugins","colors","images","classes"],hide_table_of_contents:!1},r=void 0,l={permalink:"/blog/tips-useful-features",source:"@site/blog/2024-02-29-tips-useful-features.md",title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",date:"2024-02-29T00:00:00.000Z",formattedDate:"February 29, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"blocks",permalink:"/blog/tags/blocks"},{label:"plugins",permalink:"/blog/tags/plugins"},{label:"colors",permalink:"/blog/tags/colors"},{label:"images",permalink:"/blog/tags/images"},{label:"classes",permalink:"/blog/tags/classes"}],readingTime:5.665,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",slug:"tips-useful-features",authors:"obradovic",date:"2024-02-29T00:00:00.000Z",tags:["eightshift","boilerplate","blocks","plugins","colors","images","classes"],hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"Making your project multilingual",permalink:"/blog/making-your-project-multilingual"}},n={authorsImageUrls:[void 0]},u=[];function c(e){const t={p:"p",...(0,o.M)(),...e.components};return(0,i.jsx)(t.p,{children:"Eightshift DevKit has lots of features that can make your development experience easier. These are mostly minor features which can\u2019t be a blog post of its own, so they\u2019ll be listed here. This isn\u2019t a complete list, so there may be a part 2 in the future."})}function p(e={}){const{wrapper:t}={...(0,o.M)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},4552:(e,t,s)=>{s.d(t,{I:()=>l,M:()=>r});var i=s(11504);const o={},a=i.createContext(o);function r(e){const t=i.useContext(a);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),i.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/752c6bfa.e9f0f022.js b/assets/js/752c6bfa.e9f0f022.js new file mode 100644 index 000000000..50cb31cc6 --- /dev/null +++ b/assets/js/752c6bfa.e9f0f022.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[81796],{20944:s=>{s.exports=JSON.parse('{"label":"plugins","permalink":"/blog/tags/plugins","allTagsPath":"/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.361e03c7.js b/assets/js/814f3328.361e03c7.js new file mode 100644 index 000000000..33634d32c --- /dev/null +++ b/assets/js/814f3328.361e03c7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[45512],{4352:e=>{e.exports=JSON.parse('{"title":"Latest posts","items":[{"title":"Tips & useful features","permalink":"/blog/tips-useful-features","unlisted":false},{"title":"Making your project multilingual","permalink":"/blog/making-your-project-multilingual","unlisted":false},{"title":"How to use the Wrapper as a standalone component","permalink":"/blog/wrapper-as-a-standalone-component","unlisted":false},{"title":"Working with custom queries","permalink":"/blog/working-with-custom-queries","unlisted":false},{"title":"Using multiple same components","permalink":"/blog/multiple-same-components","unlisted":false}]}')}}]); \ No newline at end of file diff --git a/assets/js/814f3328.d17fcfde.js b/assets/js/814f3328.d17fcfde.js deleted file mode 100644 index f3782a8b1..000000000 --- a/assets/js/814f3328.d17fcfde.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[45512],{4352:e=>{e.exports=JSON.parse('{"title":"Latest posts","items":[{"title":"Making your project multilingual","permalink":"/blog/making-your-project-multilingual","unlisted":false},{"title":"How to use the Wrapper as a standalone component","permalink":"/blog/wrapper-as-a-standalone-component","unlisted":false},{"title":"Working with custom queries","permalink":"/blog/working-with-custom-queries","unlisted":false},{"title":"Using multiple same components","permalink":"/blog/multiple-same-components","unlisted":false},{"title":"Block Patterns","permalink":"/blog/block-patterns","unlisted":false}]}')}}]); \ No newline at end of file diff --git a/assets/js/8962d52c.19183d4f.js b/assets/js/8962d52c.19183d4f.js new file mode 100644 index 000000000..b8d617745 --- /dev/null +++ b/assets/js/8962d52c.19183d4f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[13347],{9616:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var s=t(17624),i=t(4552);const a={title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",slug:"tips-useful-features",authors:"obradovic",date:new Date("2024-02-29T00:00:00.000Z"),tags:["eightshift","boilerplate","blocks","plugins","colors","images","classes"],hide_table_of_contents:!1},o=void 0,l={permalink:"/blog/tips-useful-features",source:"@site/blog/2024-02-29-tips-useful-features.md",title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",date:"2024-02-29T00:00:00.000Z",formattedDate:"February 29, 2024",tags:[{label:"eightshift",permalink:"/blog/tags/eightshift"},{label:"boilerplate",permalink:"/blog/tags/boilerplate"},{label:"blocks",permalink:"/blog/tags/blocks"},{label:"plugins",permalink:"/blog/tags/plugins"},{label:"colors",permalink:"/blog/tags/colors"},{label:"images",permalink:"/blog/tags/images"},{label:"classes",permalink:"/blog/tags/classes"}],readingTime:5.665,hasTruncateMarker:!0,authors:[{name:"Igor Obradovi\u0107",title:"WordPress Engineer",url:"https://github.com/iobrado",imageURL:"https://avatars.githubusercontent.com/u/23059501?v=4",key:"obradovic"}],frontMatter:{title:"Tips & useful features",description:"Various tips and useful features for your project included in Eightshift DevKit.",slug:"tips-useful-features",authors:"obradovic",date:"2024-02-29T00:00:00.000Z",tags:["eightshift","boilerplate","blocks","plugins","colors","images","classes"],hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"Making your project multilingual",permalink:"/blog/making-your-project-multilingual"}},r={authorsImageUrls:[void 0]},c=[{value:"Using only Eightshift blocks",id:"using-only-eightshift-blocks",level:2},{value:"ModifyAdminAppearance class",id:"modifyadminappearance-class",level:2},{value:"EscapedView class",id:"escapedview-class",level:2},{value:"Media class",id:"media-class",level:2},{value:"Automatic WebP conversion",id:"automatic-webp-conversion",level:2},{value:"Dynamic cookies for WP Rocket",id:"dynamic-cookies-for-wp-rocket",level:2},{value:"Yoast SEO helper",id:"yoast-seo-helper",level:2},{value:"Linters and Coding Standards checks",id:"linters-and-coding-standards-checks",level:2}];function d(e){const n={blockquote:"blockquote",code:"code",em:"em",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.M)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"Eightshift DevKit has lots of features that can make your development experience easier. These are mostly minor features which can\u2019t be a blog post of its own, so they\u2019ll be listed here. This isn\u2019t a complete list, so there may be a part 2 in the future."}),"\n",(0,s.jsx)(n.h2,{id:"using-only-eightshift-blocks",children:"Using only Eightshift blocks"}),"\n",(0,s.jsxs)(n.p,{children:["If you want to remove the default Gutenberg blocks and use only Eightshift blocks, it\u2019s as easy as adding the following code in your ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.em,{children:"src/Blocks/Blocks.php"})})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"// Limits the usage of only custom project blocks.\n\\add_filter('allowed_block_types_all', [$this, 'getAllBlocksList'], 10, 2);\n"})}),"\n",(0,s.jsx)(n.h2,{id:"modifyadminappearance-class",children:"ModifyAdminAppearance class"}),"\n",(0,s.jsxs)(n.p,{children:["When you have multiple environments (local, staging, production ...), you may accidentally change something on the wrong environment if you mix up your tabs. The ",(0,s.jsx)(n.code,{children:"ModifyAdminAppearance"})," class changes your WP Admin color scheme depending on the environment type defined with the ",(0,s.jsx)(n.code,{children:"WP_ENVIRONMENT_TYPE"})," constant. That way it\u2019s much easier to differentiate on which environment you are when you're in WP Admin."]}),"\n",(0,s.jsx)(n.p,{children:"Now when you get a call from the client asking you why the blog post they published isn\u2019t visible on the site, the first question you can ask is: \u201cWhat color is the admin area?\u201d \ud83d\ude04"}),"\n",(0,s.jsx)(n.p,{children:"To add it into your project, use the following WP-CLI command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create modify-admin-appearance\n"})}),"\n",(0,s.jsxs)(n.p,{children:["After adding this class to your project, all you have to do is define the proper environment type in the ",(0,s.jsx)(n.code,{children:"WP_ENVIRONMENT_TYPE"})," constant in ",(0,s.jsx)(n.em,{children:(0,s.jsx)(n.strong,{children:"wp-config.php."})})]}),"\n",(0,s.jsx)(n.p,{children:"The supported values are:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"local"}),"\n",(0,s.jsx)(n.li,{children:"development"}),"\n",(0,s.jsx)(n.li,{children:"staging"}),"\n",(0,s.jsx)(n.li,{children:"production"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Different color schemes in WP Admin depending on the environment",src:t(80256).c+"",width:"1042",height:"562"})}),"\n",(0,s.jsxs)(n.p,{children:["If you want to change any of the colors, you can do so by modifying the values in ",(0,s.jsx)(n.code,{children:"COLOR_SCHEMES"})," array inside ",(0,s.jsx)(n.code,{children:"ModifyAdminAppearance"})," class. For the full list of available color schemes, you can navigate to ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.em,{children:"wp-admin/css/colors"})}),". The subfolders here represent the color schemes you can use. For example, if you want to change production environment color scheme to ",(0,s.jsx)(n.code,{children:"coffee"}),", you would do it like this:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"/**\n * List of admin color schemes.\n *\n * @var array\n */\npublic const COLOR_SCHEMES = [\n\t'development' => 'fresh',\n\t'local' => 'fresh',\n\t'staging' => 'blue',\n\t'production' => 'coffee',\n];\n"})}),"\n",(0,s.jsx)(n.h2,{id:"escapedview-class",children:"EscapedView class"}),"\n",(0,s.jsx)(n.p,{children:"This class can be used to define which tags and attributes are escaped or allowed in your project. To add the class into your project, use the following WP-CLI command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create escaped-view\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Here\u2019s an example how to define the allowed tags so ",(0,s.jsx)(n.code,{children:"wp_kses"})," doesn\u2019t remove them:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\t/**\n\t * Add tags to allowed HTML list.\n\t *\n\t * @param array> $tags Allowed tags.\n\t *\n\t * @return array>\n\t */\n\tpublic function ksesAllowedHtml($tags): array\n\t{\n\t\t$tags['source'] = [\n\t\t\t'src' => [],\n\t\t\t'type' => [],\n\t\t];\n\n\t\t$tags['video'] = [\n\t\t\t'muted' => [],\n\t\t\t'src' => [],\n\t\t\t'autoplay' => [],\n\t\t\t// ...\n\t\t];\n\n\t\t$tags['div']['data-*'] = true;\n\n\t\treturn \\array_merge(\n\t\t\t$tags,\n\t\t\tstatic::SVG,\n\t\t\tstatic::FORM,\n\t\t\tstatic::IFRAME\n\t\t);\n\t}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["\u26a0\ufe0f ",(0,s.jsx)(n.strong,{children:"Note"}),"\nPlease be vary of attributes you're allowing, as that could expose your website to cross-site-scripting (XSS) and similar attacks!"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Don\u2019t forget to add this to the ",(0,s.jsx)(n.code,{children:"register"})," method:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\\add_filter('wp_kses_allowed_html', [$this, 'ksesAllowedHtml'], 20, 1);\n"})}),"\n",(0,s.jsx)(n.h2,{id:"media-class",children:"Media class"}),"\n",(0,s.jsx)(n.p,{children:"To add the Media class to your project, use the following WP-CLI command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create media\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can use the Media class to define new media sizes or allow uploads of the mime types that are not allowed by default."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\t/**\n\t * Allow additional types for uploads in media.\n\t *\n\t * @param array $mimes Load all mimes types.\n\t *\n\t * @return array Return original and updated.\n\t */\n\tpublic function enableMimeTypes(array $mimes): array\n\t{\n\t\t$mimes['svg'] = 'image/svg+xml';\n\t\t$mimes['zip'] = 'application/zip';\n\t\t$mimes['json'] = 'application/json';\n\n\t\treturn $mimes;\n\t}\n"})}),"\n",(0,s.jsx)(n.p,{children:"And register the filter:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\\add_filter('upload_mimes', [$this, 'enableMimeTypes']);\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This class also supports converting your media into ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.em,{children:"WebP"})})," format, but more on that below."]}),"\n",(0,s.jsx)(n.h2,{id:"automatic-webp-conversion",children:"Automatic WebP conversion"}),"\n",(0,s.jsx)(n.p,{children:"The WebP file format is becoming more and more popular, with its smaller file sizes and better compression, it is a preferred replacement for .jpg and .png formats. Eightshift DevKit supports converting your existing files to WebP format."}),"\n",(0,s.jsx)(n.p,{children:"If you already have the Media class in your project, you need to run this WP-CLI command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create webp-media-column\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This command will add a new column in your ",(0,s.jsx)(n.em,{children:(0,s.jsx)(n.strong,{children:"List view"})})," in Media Library that shows if the media is converted to WebP or not."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"WebP column in Media Library",src:t(66616).c+"",width:"2100",height:"834"})}),"\n",(0,s.jsx)(n.p,{children:"To enable and convert existing media to WebP, run the following commands:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate run use-webp-media\nwp boilerplate run regenerate-media\n"})}),"\n",(0,s.jsxs)(n.p,{children:["After running these commands, you\u2019ll have your images converted to WebP. Some formats like ",(0,s.jsx)(n.code,{children:"svg"})," will not be converted, this is intended."]}),"\n",(0,s.jsx)(n.p,{children:"Please note that the original files will not be deleted and you\u2019ll have to add additional logic to your Image component for replacing the URLs."}),"\n",(0,s.jsx)(n.p,{children:"Here\u2019s a simple example how to do it:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"// Check if webP is used from admin.\n$isWebPUsed = Components::isWebPMediaUsed();\n\n// If webP is used override the url for large.\nif ($isWebPUsed) {\n\t$sourceWebPLarge = Components::getWebPMedia($imageUrl['large'], Media::WEBP_ALLOWED_EXT);\n\n\tif ($sourceWebPLarge) {\n\t\t$imageUrl['large'] = $sourceWebPLarge['src'];\n\t}\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"dynamic-cookies-for-wp-rocket",children:"Dynamic cookies for WP Rocket"}),"\n",(0,s.jsx)(n.p,{children:"This is quite useful when you have a GDPR plugin and you have to take the cookie value into consideration when serving the cached version of the site."}),"\n",(0,s.jsx)(n.p,{children:"To add the GDPR cookie to the list of dynamic cookies, create a new class called Rocket with the following WP-CLI command:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wp boilerplate create service-example --folder=Plugins/Rocket --file_name=Rocket\n"})}),"\n",(0,s.jsx)(n.p,{children:"Add the following method to the Rocket class:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\t/**\n\t * List all dynamic cookies that will create new cached version.\n\t *\n\t * @param array $items Items from the admin.\n\t *\n\t * @return array\n\t */\n\tpublic function dynamicCookiesList(array $items): array\n\t{\n\t\t$items[] = 'gdpr';\n\n\t\treturn $items;\n\t}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Finally, add the following filter to the ",(0,s.jsx)(n.code,{children:"register"})," method:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"\\add_filter('rocket_cache_dynamic_cookies', [$this, 'dynamicCookiesList']);\n"})}),"\n",(0,s.jsx)(n.h2,{id:"yoast-seo-helper",children:"Yoast SEO helper"}),"\n",(0,s.jsx)(n.p,{children:"The content generated by Eightshift blocks may be difficult for Yoast SEO to interpret, but there is a Yoast SEO helper included in our DevKit that fixes the issue and makes the content readable to Yoast SEO. By default, Yoast SEO can't read the content from custom dynamic blocks, so this helper tells it how to parse the content from the blocks."}),"\n",(0,s.jsxs)(n.p,{children:["To enable this, you have to add the following in ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.em,{children:"src/Blocks/assets/scripts/application-blocks-editor.js"})}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:"import { yoastSeo } from '@eightshift/frontend-libs/scripts/plugins';\n\n// ...\n\nyoastSeo();\n"})}),"\n",(0,s.jsx)(n.h2,{id:"linters-and-coding-standards-checks",children:"Linters and Coding Standards checks"}),"\n",(0,s.jsx)(n.p,{children:"Eightshift DevKit includes coding standards checks that will help you improve your code quality."}),"\n",(0,s.jsx)(n.p,{children:"To run these commands, you have to be in the theme root folder. Here\u2019s the list:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"npm run lintStyle"})," - check your CSS files with StyleLint"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"npm run lintJs"})," - check your JS files with ESLint"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"composer test:standards"})," - check PHP coding standards errors with PHPCS"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"composer test:types"})," - runs PHPStan static code analysis"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,i.M)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},80256:(e,n,t)=>{t.d(n,{c:()=>s});const s=t.p+"assets/images/modify-admin-appearance-cc0e975f95d5dce4062cdeaf403a37d0.webp"},66616:(e,n,t)=>{t.d(n,{c:()=>s});const s=t.p+"assets/images/webp-column-5f5ad6cace4b91fa19882b3060fda7b0.webp"},4552:(e,n,t)=>{t.d(n,{I:()=>l,M:()=>o});var s=t(11504);const i={},a=s.createContext(i);function o(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/8eb4e46b.b29d9b47.js b/assets/js/8eb4e46b.eb1124b3.js similarity index 83% rename from assets/js/8eb4e46b.b29d9b47.js rename to assets/js/8eb4e46b.eb1124b3.js index 2c3126c80..737ef50fc 100644 --- a/assets/js/8eb4e46b.b29d9b47.js +++ b/assets/js/8eb4e46b.eb1124b3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[92296],{48376:t=>{t.exports=JSON.parse('{"permalink":"/blog/page/2","page":2,"postsPerPage":9,"totalPages":2,"totalCount":14,"previousPage":"/blog","blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[92296],{48376:t=>{t.exports=JSON.parse('{"permalink":"/blog/page/2","page":2,"postsPerPage":9,"totalPages":2,"totalCount":15,"previousPage":"/blog","blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file diff --git a/assets/js/9a46388f.f1a4248d.js b/assets/js/9a46388f.d52cf9d2.js similarity index 75% rename from assets/js/9a46388f.f1a4248d.js rename to assets/js/9a46388f.d52cf9d2.js index 24636db2a..b3ff835f2 100644 --- a/assets/js/9a46388f.f1a4248d.js +++ b/assets/js/9a46388f.d52cf9d2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[2968],{48256:e=>{e.exports=JSON.parse('{"label":"boilerplate","permalink":"/blog/tags/boilerplate","allTagsPath":"/blog/tags","count":14,"unlisted":false}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[2968],{48256:e=>{e.exports=JSON.parse('{"label":"boilerplate","permalink":"/blog/tags/boilerplate","allTagsPath":"/blog/tags","count":15,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/9bd02e6c.15e5a506.js b/assets/js/9bd02e6c.52b001a2.js similarity index 70% rename from assets/js/9bd02e6c.15e5a506.js rename to assets/js/9bd02e6c.52b001a2.js index a0b43f587..aa4388829 100644 --- a/assets/js/9bd02e6c.15e5a506.js +++ b/assets/js/9bd02e6c.52b001a2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[22472],{23316:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>a,contentTitle:()=>l,default:()=>p,frontMatter:()=>o,metadata:()=>r,toc:()=>c});var n=s(17624),i=s(4552);const o={id:"wp-cli",title:"WP-CLI"},l=void 0,r={id:"php/wp-cli",title:"WP-CLI",description:"What is WP-CLI?",source:"@site/forms/php/wp-cli.md",sourceDirName:"php",slug:"/php/wp-cli",permalink:"/forms/php/wp-cli",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"wp-cli",title:"WP-CLI"},sidebar:"forms",previous:{title:"Helpers",permalink:"/forms/php/helpers"},next:{title:"How to use?",permalink:"/forms/php/filters/how-to-use"}},a={},c=[{value:"What is WP-CLI?",id:"what-is-wp-cli",level:2},{value:"Available commands",id:"available-commands",level:2},{value:"copy_stylesheet_set",id:"copy_stylesheet_set",level:3}];function d(e){const t={admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.M)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.h2,{id:"what-is-wp-cli",children:"What is WP-CLI?"}),"\n",(0,n.jsx)(t.p,{children:"WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs and much more, without using a web browser."}),"\n",(0,n.jsx)(t.h2,{id:"available-commands",children:"Available commands"}),"\n",(0,n.jsx)(t.p,{children:"Eightshift Forms offers a few commands that can be used to speed up your development process."}),"\n",(0,n.jsx)(t.h3,{id:"copy_stylesheet_set",children:"copy_stylesheet_set"}),"\n",(0,n.jsxs)(t.p,{children:["This stylesheet set is used to give you a head start in providing your own style to the form. It will copy a predefined stylesheet set to your active theme as a new ",(0,n.jsx)(t.code,{children:"component"})," and give you all the files necessary to provide your own style."]}),"\n",(0,n.jsx)(t.admonition,{type:"note",children:(0,n.jsx)(t.p,{children:"After adding a custom stylesheet, make sure to deactivate built-in styles in Global settings!"})}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-bash",children:"wp eightshift-forms esf copy_stylesheet_set\n"})}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Available options"}),":"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"--additional-path="})," - Set additional path relative from the active theme. Example. ",(0,n.jsx)(t.code,{children:"src/Block/components/"}),"."]}),"\n"]})]})}function p(e={}){const{wrapper:t}={...(0,i.M)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},4552:(e,t,s)=>{s.d(t,{I:()=>r,M:()=>l});var n=s(11504);const i={},o=n.createContext(i);function l(e){const t=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[22472],{23316:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>a,contentTitle:()=>l,default:()=>p,frontMatter:()=>o,metadata:()=>r,toc:()=>c});var n=s(17624),i=s(4552);const o={id:"wp-cli",title:"WP-CLI"},l=void 0,r={id:"php/wp-cli",title:"WP-CLI",description:"What is WP-CLI?",source:"@site/forms/php/wp-cli.md",sourceDirName:"php",slug:"/php/wp-cli",permalink:"/forms/php/wp-cli",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"wp-cli",title:"WP-CLI"},sidebar:"forms",previous:{title:"Helpers",permalink:"/forms/php/helpers"},next:{title:"How to use?",permalink:"/forms/php/filters/how-to-use"}},a={},c=[{value:"What is WP-CLI?",id:"what-is-wp-cli",level:2},{value:"Available commands",id:"available-commands",level:2},{value:"copy_stylesheet_set",id:"copy_stylesheet_set",level:3}];function d(e){const t={admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.M)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.h2,{id:"what-is-wp-cli",children:"What is WP-CLI?"}),"\n",(0,n.jsx)(t.p,{children:"WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs and much more, without using a web browser."}),"\n",(0,n.jsx)(t.h2,{id:"available-commands",children:"Available commands"}),"\n",(0,n.jsx)(t.p,{children:"Eightshift Forms offers a few commands that can be used to speed up your development process."}),"\n",(0,n.jsx)(t.h3,{id:"copy_stylesheet_set",children:"copy_stylesheet_set"}),"\n",(0,n.jsxs)(t.p,{children:["This stylesheet set is used to give you a head start in providing your own style to the form. It will copy a predefined stylesheet set to your active theme as a new ",(0,n.jsx)(t.code,{children:"component"})," and give you all the files necessary to provide your own style."]}),"\n",(0,n.jsx)(t.admonition,{type:"note",children:(0,n.jsx)(t.p,{children:"After adding a custom stylesheet, make sure to deactivate built-in styles in Global settings!"})}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-bash",children:"wp eightshift-forms copy_stylesheet_set\n"})}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Available options"}),":"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"--additional-path="})," - Set additional path relative from the active theme. Example. ",(0,n.jsx)(t.code,{children:"src/Blocks/components"}),"."]}),"\n"]})]})}function p(e={}){const{wrapper:t}={...(0,i.M)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},4552:(e,t,s)=>{s.d(t,{I:()=>r,M:()=>l});var n=s(11504);const i={},o=n.createContext(i);function l(e){const t=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a7023ddc.53ccd913.js b/assets/js/a7023ddc.53ccd913.js deleted file mode 100644 index ed1e44018..000000000 --- a/assets/js/a7023ddc.53ccd913.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[39112],{61568:l=>{l.exports=JSON.parse('[{"label":"eightshift","permalink":"/blog/tags/eightshift","count":14},{"label":"boilerplate","permalink":"/blog/tags/boilerplate","count":14},{"label":"i18n","permalink":"/blog/tags/i-18-n","count":1},{"label":"multilingual","permalink":"/blog/tags/multilingual","count":1},{"label":"wrapper","permalink":"/blog/tags/wrapper","count":1},{"label":"components","permalink":"/blog/tags/components","count":5},{"label":"service","permalink":"/blog/tags/service","count":1},{"label":"class","permalink":"/blog/tags/class","count":1},{"label":"query","permalink":"/blog/tags/query","count":1},{"label":"blocks","permalink":"/blog/tags/blocks","count":4},{"label":"block","permalink":"/blog/tags/block","count":2},{"label":"patterns","permalink":"/blog/tags/patterns","count":1},{"label":"cpt","permalink":"/blog/tags/cpt","count":1},{"label":"custom post type","permalink":"/blog/tags/custom-post-type","count":1},{"label":"taxonomy","permalink":"/blog/tags/taxonomy","count":1},{"label":"taxonomies","permalink":"/blog/tags/taxonomies","count":1},{"label":"terms","permalink":"/blog/tags/terms","count":1},{"label":"variations","permalink":"/blog/tags/variations","count":1},{"label":"acf","permalink":"/blog/tags/acf","count":1},{"label":"advanced custom fields","permalink":"/blog/tags/advanced-custom-fields","count":1},{"label":"theme options","permalink":"/blog/tags/theme-options","count":1},{"label":"assets","permalink":"/blog/tags/assets","count":1},{"label":"images","permalink":"/blog/tags/images","count":1},{"label":"icons","permalink":"/blog/tags/icons","count":1},{"label":"fonts","permalink":"/blog/tags/fonts","count":1},{"label":"wpcli","permalink":"/blog/tags/wpcli","count":1},{"label":"setup","permalink":"/blog/tags/setup","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/a7023ddc.754dadb6.js b/assets/js/a7023ddc.754dadb6.js new file mode 100644 index 000000000..04e2d8565 --- /dev/null +++ b/assets/js/a7023ddc.754dadb6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[39112],{61568:l=>{l.exports=JSON.parse('[{"label":"eightshift","permalink":"/blog/tags/eightshift","count":15},{"label":"boilerplate","permalink":"/blog/tags/boilerplate","count":15},{"label":"blocks","permalink":"/blog/tags/blocks","count":5},{"label":"plugins","permalink":"/blog/tags/plugins","count":1},{"label":"colors","permalink":"/blog/tags/colors","count":1},{"label":"images","permalink":"/blog/tags/images","count":2},{"label":"classes","permalink":"/blog/tags/classes","count":1},{"label":"i18n","permalink":"/blog/tags/i-18-n","count":1},{"label":"multilingual","permalink":"/blog/tags/multilingual","count":1},{"label":"wrapper","permalink":"/blog/tags/wrapper","count":1},{"label":"components","permalink":"/blog/tags/components","count":5},{"label":"service","permalink":"/blog/tags/service","count":1},{"label":"class","permalink":"/blog/tags/class","count":1},{"label":"query","permalink":"/blog/tags/query","count":1},{"label":"block","permalink":"/blog/tags/block","count":2},{"label":"patterns","permalink":"/blog/tags/patterns","count":1},{"label":"cpt","permalink":"/blog/tags/cpt","count":1},{"label":"custom post type","permalink":"/blog/tags/custom-post-type","count":1},{"label":"taxonomy","permalink":"/blog/tags/taxonomy","count":1},{"label":"taxonomies","permalink":"/blog/tags/taxonomies","count":1},{"label":"terms","permalink":"/blog/tags/terms","count":1},{"label":"variations","permalink":"/blog/tags/variations","count":1},{"label":"acf","permalink":"/blog/tags/acf","count":1},{"label":"advanced custom fields","permalink":"/blog/tags/advanced-custom-fields","count":1},{"label":"theme options","permalink":"/blog/tags/theme-options","count":1},{"label":"assets","permalink":"/blog/tags/assets","count":1},{"label":"icons","permalink":"/blog/tags/icons","count":1},{"label":"fonts","permalink":"/blog/tags/fonts","count":1},{"label":"wpcli","permalink":"/blog/tags/wpcli","count":1},{"label":"setup","permalink":"/blog/tags/setup","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/a9ff9f98.71445290.js b/assets/js/a9ff9f98.b4ce5993.js similarity index 75% rename from assets/js/a9ff9f98.71445290.js rename to assets/js/a9ff9f98.b4ce5993.js index c1c0d61c5..bfe27dcdf 100644 --- a/assets/js/a9ff9f98.71445290.js +++ b/assets/js/a9ff9f98.b4ce5993.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[71760],{57048:s=>{s.exports=JSON.parse('{"label":"eightshift","permalink":"/blog/tags/eightshift","allTagsPath":"/blog/tags","count":14,"unlisted":false}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[71760],{57048:s=>{s.exports=JSON.parse('{"label":"eightshift","permalink":"/blog/tags/eightshift","allTagsPath":"/blog/tags","count":15,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/b101fbec.2a6ed35a.js b/assets/js/b101fbec.2a6ed35a.js new file mode 100644 index 000000000..f2ab49756 --- /dev/null +++ b/assets/js/b101fbec.2a6ed35a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[75124],{86896:t=>{t.exports=JSON.parse('{"permalink":"/blog/tags/classes","page":1,"postsPerPage":9,"totalPages":1,"totalCount":1,"blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file diff --git a/assets/js/b2b675dd.530c7221.js b/assets/js/b2b675dd.85889ac2.js similarity index 83% rename from assets/js/b2b675dd.530c7221.js rename to assets/js/b2b675dd.85889ac2.js index ef1385d41..5477b4891 100644 --- a/assets/js/b2b675dd.530c7221.js +++ b/assets/js/b2b675dd.85889ac2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[16292],{76180:t=>{t.exports=JSON.parse('{"permalink":"/blog","page":1,"postsPerPage":9,"totalPages":2,"totalCount":14,"nextPage":"/blog/page/2","blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file +"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[16292],{76180:t=>{t.exports=JSON.parse('{"permalink":"/blog","page":1,"postsPerPage":9,"totalPages":2,"totalCount":15,"nextPage":"/blog/page/2","blogDescription":"Tutorials and articles about Eightshift development kit","blogTitle":"Tutorials and articles about Eightshift development kit"}')}}]); \ No newline at end of file diff --git a/assets/js/b2f554cd.15cf2448.js b/assets/js/b2f554cd.15cf2448.js deleted file mode 100644 index f019b0463..000000000 --- a/assets/js/b2f554cd.15cf2448.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_eightshift_docs=self.webpackChunk_eightshift_docs||[]).push([[86880],{38256:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"making-your-project-multilingual","metadata":{"permalink":"/blog/making-your-project-multilingual","source":"@site/blog/2024-02-01-making-your-project-multilingual.md","title":"Making your project multilingual","description":"Examples of using I18n in a project","date":"2024-02-01T00:00:00.000Z","formattedDate":"February 1, 2024","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"i18n","permalink":"/blog/tags/i-18-n"},{"label":"multilingual","permalink":"/blog/tags/multilingual"}],"readingTime":5.75,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Making your project multilingual","description":"Examples of using I18n in a project","slug":"making-your-project-multilingual","authors":"obradovic","date":"2024-02-01T00:00:00.000Z","tags":["eightshift","boilerplate","i18n","multilingual"],"hide_table_of_contents":false},"unlisted":false,"nextItem":{"title":"How to use the Wrapper as a standalone component","permalink":"/blog/wrapper-as-a-standalone-component"}},"content":"Tools like Google Translate can automatically translate websites with reasonable quality. However, users will have a much better experience if you add support for multiple languages in your project and manage the translations yourself.\\n\x3c!--truncate--\x3e\\n\\n## Making strings translatable in PHP\\nA good practice is to use one of the I18n (internationalization) functions for your hardcoded strings, even if your website starts with a single language. This way, you can add multilingual support more easily later.\\n\\nIf you\'ve worked on a multilanguage-capable project, you most likely came across `__()` and `_e()` functions. The main difference between the `__()` and `_e()` is that `__()` returns the value, while `_e()` echoes it. Both functions take two arguments: the first one is the string to be translated, and the second one is the textdomain that identifies the translation file.\\n\\n> Textdomain is usually your project name written in kebab-case.\\n\\nWhile WordPress functions like `__()` and `_e()` will definitely do the job, it is much better to use the variants of these functions that also escape the output. These are `esc_html__()` and `esc_html_e()`. There are also a few more functions for I18n you can use, but to keep it simple, we\'ll just mention these two for now.\\n\\nHere is an example of using one of these functions:\\n```php\\n\\n```\\n\\n## Making strings translatable in JS\\nTo translate the strings in the Block editor or options, you will first have to import the function from the `@wordpress/i18n` library.\\n```jsx\\nimport { __ } from \'@wordpress/i18n\';\\n```\\nTo output your string, simply use it like this:\\n```jsx\\n{__(\'Icon position\', \'project-name\')}\\n```\\n\\nAlternative functions you can use are:\\n- `_n` for singular/plural forms\\n- `_nx` for singular/plural forms with _gettext_ context\\n- `_x` for a translated string with a _gettext_ context\\n\\nYou can refer to the [block editor handbook](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/) for more information on these functions.\\n\\n## The I18n class\\nThe easiest way to add I18n support to a project created with Eightshift boilerplate is by using the WP-CLI command:\\n```bash\\nwp boilerplate create i18n\\n```\\n\\nThis command generated a new class inside the `src/I18n` folder. This class instructs WordPress to look for translations in `src/I18n/languages` with the textdomain defined as your project name. The next step is generating .po and .mo files that are used for translation.\\n\\n## Generating .pot file\\nYou can create a `.pot` (_Portable object template_) file by using WP-CLI. Run the following command in your project root:\\n```bash\\nwp i18n make-pot\\n```\\n\\nAlternatively, you can use tools like [Poedit](https://poedit.net/) to generate a `.pot` file and generate translations from it later.\\n\\n## Translating with Poedit\\nOnce you have the `.pot` file, you can use Poedit to generate `.po` and `.mo` files that are used for translating hardcoded strings in your project. When generating the files, you can choose for which locale you\'re creating the translation for. For example, if you are creating a translation for the German language, your files should be named `de_DE.po` and `de_DE.mo`.\\n\\nAfter generating the files, go to **Translation -> Properties** and navigate to the **Sources Paths** tab. Set the _Base path_ to the theme folder path. In _Excluded paths_ you can add folders like `node_modules`, `vendor`, and `public` to exclude external packages.\\n\\nIn the _Sources keywords_ tab you can set additional functions for use in your project for translations. Commonly used functions are:\\n- `_e` for translating a string and echoing it\\n- `__` for returning a translated string\\n- `esc_html__` for returning a translated string which is escaped in a way it\'s safe to use within HTML\\n- `esc_html_e` for echoing a translated string which is escaped in a way it\'s safe to use within HTML\\n- `esc_attr__` for returning a translated string which is escaped in a way it\'s safe to use within an attribute\\n- `esc_html_x` for returning a translated string which is escaped in a way it\'s safe to use within HTML, with a _gettext_ context\\n- `_n` for returning a translated string in a singular or plural form, based on the supplied number\\n\\n> If you\'re missing a string in your `.po` file be sure to check which function is used for translation for that string, and that the function is added to _Sources keywords_.\\n\\nAfter updating the settings, click on _Update from source code_ option to get the updated list of strings to translate.\\n\\nThe translation process is simple. The left column represents the source text, and the right column the translation. When you have finished translating the strings, copy the `.po` and `.mo` files to the `src/I18n/languages` folder.\\n\\n## JS translations\\nThe process of translating strings in JS has a couple of extra steps.\\n\\nIn order to translate strings in JS (e.g. Block editor strings), you will have to generate translation file. To do this, navigate to your `src/I18n/languages` folder and use the following WP-CLI command:\\n```bash\\nwp i18n make-json --no-purge\\n```\\n\\nThis will generate a `.json` file for each JS file present. The strings are extracted from `.po` files, so you\'ll already have the translations added. The `--no-purge` flag is used to keep the existing translations in the `.po` file.\\n\\nThe method used for setting the script translations is `setScriptTranslations()` from the `I18n` class.\\n\\nThe default way this works in Eightshift DevKit is that you need to have a single `.json` file with all the JS translations. If needed, you can either modify this method to read from multiple files, or just merge all the `.json` files into one.\\n\\nIf using the default setup (everything in one file) follow this naming structure: `{textdomain}-{locale}-{handle}.json`.\\n\\nFor example, if your _textdomain_ is `project-name` and your locale is `de_DE`, your file should be named `project-name-de_DE-project-name-block-editor-scripts.json`.\\n\\n> The block-related translations depend on the language the user has set in WP admin.\\n\\n## Enabling languages and content translation\\nIf the website itself needs to support content in multiple languages, a plugin is a good option.\\n\\n The most common multi-language plugins are:\\n- **WPML** - one of the most popular plugins on the market. It is a paid plugin, but offers a lot of advanced options.\\n- **Polylang** - a free plugin (also has a paid _Pro_ version).\\n\\n> Explore other options as well, you might find a plugin that is a better fit for your project than WPML or Polylang.\\n\\nMost of the translation work will be done through the editor, since you\'ll need to translate the content on posts and pages.\\n\\n## Additional resources\\nInternationalization (_I18n_) and Localization (_L10n_) are very broad topics, so it\'s impossible to cover everything in a single blog post.\\n\\nIf you wish to know about the core I18n functionalities, or a bit more about how it is used in the Eightshift DevKit, here are a few resources which you may find interesting:\\n- [WordPress Codex - I18n for WordPress Developers](https://codex.wordpress.org/I18n_for_WordPress_Developers)\\n- [Eightshift Development kit documentation - Tips & Tricks](https://eightshift.com/docs/basics/tips-tricks/#internationalization-i18n-and-localization-l10n)\\n- [Infinum WordPress Handbook - Localization](https://infinum.com/handbook/wordpress/translations/localization)"},{"id":"wrapper-as-a-standalone-component","metadata":{"permalink":"/blog/wrapper-as-a-standalone-component","source":"@site/blog/2023-09-05-wrapper-as-a-standalone-component.md","title":"How to use the Wrapper as a standalone component","description":"Explains the process of using the Wrapper component in WordPress templates.","date":"2023-09-05T00:00:00.000Z","formattedDate":"September 5, 2023","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"wrapper","permalink":"/blog/tags/wrapper"},{"label":"components","permalink":"/blog/tags/components"}],"readingTime":3.205,"hasTruncateMarker":true,"authors":[{"name":"Ivan Kancijan","title":"WordPress Engineer","url":"https://github.com/kancijan","imageURL":"https://avatars.githubusercontent.com/u/135589039?v=4","key":"kancijan"}],"frontMatter":{"title":"How to use the Wrapper as a standalone component","description":"Explains the process of using the Wrapper component in WordPress templates.","slug":"wrapper-as-a-standalone-component","authors":"kancijan","date":"2023-09-05T00:00:00.000Z","tags":["eightshift","boilerplate","wrapper","components"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Making your project multilingual","permalink":"/blog/making-your-project-multilingual"},"nextItem":{"title":"Working with custom queries","permalink":"/blog/working-with-custom-queries"}},"content":"As one of the most powerful features in the Eightshift DevKit, the Wrapper is a part of every Eightshift block in the Gutenberg editor, but what about WordPress templates?\\n\x3c!--truncate--\x3e\\n\\n:::note\\n[Wrapper](https://eightshift.com/docs/basics/blocks-wrapper/) is designed to be the ultimate top-level component that controls how your block behaves in the website layout. It is a sort of a \'section\' in traditional builders. By default, you can control a whole lot of stuff, but there is an option to add custom attributes and tailor the Wrapper to the needs of your project.\\n:::\\n\\n## What are WordPress templates?\\n\\nBefore the days of [Full Site Editing](https://developer.wordpress.org/block-editor/getting-started/full-site-editing/) in WordPress, we used [template files](https://developer.wordpress.org/themes/basics/template-hierarchy/) to add custom layouts to a blog page or post archives.\\n\\n:::note\\nWhile Full-site editing (FSE) _is_ supported in Eightshift Libs, a couple of modifications have to be done in your project to make it work.\\n:::\\n\\nDepending on your setup, you might still use those as they are still a core part of WordPress themes.\\n\\n## Usage in templates\\n\\nThanks to the `Components` helper, we can easily `render()` any component in our template. \\n\\n:::note\\nInterested in how to use components in a block? Take a look at [our docs](https://eightshift.com/docs/basics/blocks-component-in-block#i-have-a-component-that-i-want-to-use-manually).\\n:::\\n\\nFor this example, we\'ll use `index.php`, as you already have it in your theme. If not, use the code below:\\n\\n```php\\n true,\\n\\t\\t\'wrapperManualContent\' => \'\',\\n\\t],\\n\\t\'\',\\n\\ttrue\\n);\\n```\\n:::note\\nSetting the `$useComponentDefaults` to `true` will save you the trouble of setting a lot of additional properties by using default values defined in your manifest.\\n:::\\n\\n### Wrapper output\\n\\nNow that we have a working Wrapper component in our template, it\'s time to display posts in the loop.\\n\\nLet\'s use the [Card](https://infinum.github.io/eightshift-frontend-libs/storybook/?path=/story/components-card--editor) from the Frontend Libs as it\'s the perfect component to display post details and pass it to the `wrapperManualContent`.\\n\\nTo make the Card component look even better, we\'ll use some of the powerful properties the Wrapper component has to offer and add spacing between each item.\\n\\n```php\\n\'wrapperSpacingTopLarge\' => 50,\\n\'wrapperSpacingBottomLarge\' => 50,\\n```\\n\\n:::note\\nFor the list of all available properties, look at the Wrapper\'s `manifest.json`.\\n:::\\n\\n### Final result\\n\\n```php\\n true,\\n\\t\\t\\t\\t\'wrapperManualContent\' => Components::render(\'card\', [\\n\\t\\t\\t\\t\\t\'introContent\' => sprintf(__(\'On %1$s by %2$s\', \'eightshift\'), get_the_date(), get_the_author_meta(\'display_name\')),\\n\\t\\t\\t\\t\\t\'headingContent\' => get_the_title(),\\n\\t\\t\\t\\t\\t\'paragraphContent\' => apply_filters(\'the_content\', get_the_excerpt()),\\n\\t\\t\\t\\t\\t\'buttonContent\' => __(\'View more\', \'eightshift\'),\\n\\t\\t\\t\\t\\t\'buttonUrl\' => get_permalink(),\\n\\t\\t\\t\\t]),\\n\\t\\t\\t\\t\'wrapperSpacingTopLarge\' => 50,\\n\\t\\t\\t\\t\'wrapperSpacingBottomLarge\' => 50,\\n\\t\\t\\t],\\n\\t\\t\\t\'\',\\n\\t\\t\\ttrue\\n\\t\\t);\\n\\t}\\n}\\n\\nget_footer();\\n```\\n![Wrapper with content](/img/blog/wrapper-content.png)\\n\\n## Conclusion\\n\\nAlthough the Wrapper is (usually) not intended to be used as a standalone component, there is a nice benefit to having a time-saving, out-of-the-box solution for displaying content in a grid already defined in your project."},{"id":"working-with-custom-queries","metadata":{"permalink":"/blog/working-with-custom-queries","source":"@site/blog/2023-08-03-adding-custom-query.md","title":"Working with custom queries","description":"Explains the process of registering a new service class, adding a custom query and using it in a block.","date":"2023-08-01T00:00:00.000Z","formattedDate":"August 1, 2023","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"service","permalink":"/blog/tags/service"},{"label":"class","permalink":"/blog/tags/class"},{"label":"query","permalink":"/blog/tags/query"}],"readingTime":3.995,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Working with custom queries","description":"Explains the process of registering a new service class, adding a custom query and using it in a block.","slug":"working-with-custom-queries","authors":"obradovic","date":"2023-08-01T00:00:00.000Z","tags":["eightshift","boilerplate","service","class","query"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"How to use the Wrapper as a standalone component","permalink":"/blog/wrapper-as-a-standalone-component"},"nextItem":{"title":"Using multiple same components","permalink":"/blog/multiple-same-components"}},"content":"Eightshift DevKit offers some blocks with query logic out of the box, but what is the best approach when you need to add a custom query to a block you\u2019ve been working on?\\n\x3c!--truncate--\x3e\\n\\n## Usage in built-in blocks\\n\\nOne of the Eightshift blocks that already uses `WP_Query` is the ***Featured Content*** block. In that block, you can see the query logic is inside the block. However, there is a much better way to do it. The reason it was done like this was to simplify this block and to have an already functioning block available with one WP-CLI command.\\n\\nA much better approach would be to separate the query logic from the block. Other than following the MVC architecture more closely, this will also make the query logic more reusable. To do this, we\u2019re gonna create a service class.\\n\\n## What are Service classes?\\n\\n*Put simply, a Service is any PHP object that performs some sort of \\"global\\" task. - Symfony docs*\\n\\nIf you take a look at the `ServiceInterface` interface, you\u2019ll notice it only contains the `register()` method. This method holds action and filter hooks for that class. Other than hooking into existing actions and filters, this method can be used to register our own filters which can be used in blocks or other classes.\\n\\nBasically, whenever you need to hook into actions or filters, you should use a Service class for that.\\n\\n## Creating a new service class\\n\\nCreating a new service class in your project is as simple as using the following WP-CLI command:\\n\\n```bash\\nwp boilerplate create service-example --folder=CustomQuery --file_name=CustomQuery\\n```\\n\\nOnce this new class is generated, you can add a new public method that will contain the query logic. We want our method to accept three optional arguments:\\n\\n- ID of the category\\n- number of posts per page\\n- number of the current page\\n\\nHaving the category ID optional will allow for one more use case, and that is fetching the latest posts regardless of category. By default, WordPress sorts the posts by publish date, from newest to oldest.\\n\\n```php\\n/**\\n * Get posts by category ID.\\n *\\n * @param int $categoryId Category ID.\\n * @param int $postsPerPage Number of posts per page.\\n * @param int $currentPage Current page number.\\n *\\n * @return WP_Query Query object.\\n */\\npublic function getPostsByCategory($categoryId = null, $postsPerPage = 3, $currentPage = 1): WP_Query\\n{\\n\\t$postArgs = [\\n\\t\\t\'post_type\' => \'post\',\\n\\t\\t\'cat\' => $categoryId,\\n\\t\\t\'posts_per_page\' => $postsPerPage,\\n\\t\\t\'paged\' => $currentPage,\\n\\t];\\n\\n\\treturn new WP_Query($postArgs);\\n}\\n```\\n\\nTo use this method, we can add it as a filter. The filter name should be added as a constant for easier maintenance. Inside the `register()` method, add the following:\\n\\n```php\\n\\\\add_filter(self::GET_POSTS_BY_CATEGORY, [$this, \'getPostsByCategory\'], 10, 3);\\n```\\n\\n## Using the new filter\\n\\n:::note\\nEven if you register a filter for a method that doesn\u2019t accept any arguments, when calling `apply_filters`, you must pass at least 1 parameter. In those cases, simply add `null` as a parameter.\\n:::\\n\\nHere are some examples how you can use the filter:\\n\\n```php\\n// Get 3 latest posts, regardless of category.\\n$allLatestPosts = apply_filters(CustomQuery::GET_POSTS_BY_CATEGORY, null);\\n\\n// Get 10 latest posts from the News category. News category ID is 2.\\n$latestNews = apply_filters(CustomQuery::GET_POSTS_BY_CATEGORY, 2, 10);\\n\\n// Get another page of News category.\\n$pagedNews = apply_filters(CustomQuery::GET_POSTS_BY_CATEGORY, 2, 10, $currentPage);\\n```\\n\\nThe above example shows multiple use cases. The first two examples could be used in a simple block that displays only the selected number of the latest posts. The last example may be used in a REST route for a load more functionality or in a block with classic pagination.\\n\\nNow you can do a regular query loop in your block to display the posts:\\n\\n```php\\nif ($latestNews->have_posts()) {\\n\\twhile ($latestNews->have_posts()) {\\n\\t\\t$latestNews->the_post();\\n\\t\\t// render the card here with the Components::render helper.\\n\\t}\\n}\\nwp_reset_postdata();\\n```\\n\\n:::caution Important\\nDon\'t forget to add `wp_reset_postdata()` after looping through the custom query!\\n:::\\n\\n## Prepare only the data you need\\n\\nIf you would like to improve this even further, you can run the `have_posts()` loop inside the class and save only the data you need to render to an array. This makes the logic even more separated from the view and all you have to do in a block is loop through the array and populate the component attributes with the values from the array.\\n\\n```php\\n$postData = [];\\n\\nif ($queryData->have_posts()) {\\n\\twhile($queryData->have_posts()) {\\n\\t\\t$queryData->the_post();\\n\\n\\t\\t$postData[] = [\\n\\t\\t\\t\'id\' => get_the_ID(),\\n\\t\\t\\t\'title\' => get_the_title(),\\n\\t\\t\\t\'url\' => get_permalink(),\\n\\t\\t\\t\'image\' => get_the_post_thumbnail_url(),\\n\\t\\t\\t\'date\' => get_the_time(\'d.m.Y.\'),\\n\\t\\t\\t\'excerpt\' => get_the_excerpt(),\\n\\t\\t];\\n\\t}\\n}\\n\\nwp_reset_postdata();\\n\\nreturn $postData;\\n```\\n\\n## Best practices for queries\\n\\nIt\u2019s important to have query optimisation in mind. Some queries may be a lot slower and you have to see if there is any way to follow the [best practices for database queries](https://infinum.com/handbook/wordpress/coding-standards/php-coding-standards/database-queries)."},{"id":"multiple-same-components","metadata":{"permalink":"/blog/multiple-same-components","source":"@site/blog/2023-06-12-multiple-same-components.md","title":"Using multiple same components","description":"Explains how to use multiple same components inside","date":"2023-06-12T00:00:00.000Z","formattedDate":"June 12, 2023","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"components","permalink":"/blog/tags/components"},{"label":"blocks","permalink":"/blog/tags/blocks"}],"readingTime":6.305,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Using multiple same components","description":"Explains how to use multiple same components inside","slug":"multiple-same-components","authors":"obradovic","date":"2023-06-12T00:00:00.000Z","tags":["eightshift","boilerplate","components","blocks"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Working with custom queries","permalink":"/blog/working-with-custom-queries"},"nextItem":{"title":"Block Patterns","permalink":"/blog/block-patterns"}},"content":"From time to time, you may need to create a block or a more complex component that uses more than one instance of the same component. In this blog post, we\'ll explain how this works in a bit more detail.\\n\x3c!--truncate--\x3e\\n\\nAn example of this use case is the Card component, which uses two heading components. You may get the general idea by just going through the code and trying to reverse-engineer it, but this example will give you a much better understanding of how it works and how to use it. First, let\'s cover some basics.\\n\\n## Manifest and attributes\\nThe way multiple same components work is by having a different key. Here is an example of the Card component:\\n\\n```json\\n\\"components\\": {\\n\\t\\"image\\": \\"image\\",\\n\\t\\"intro\\": \\"heading\\",\\n\\t\\"heading\\": \\"heading\\",\\n\\t\\"paragraph\\": \\"paragraph\\",\\n\\t\\"button\\": \\"button\\"\\n},\\n```\\nAs you can see, one heading component has the `intro` key, while the other one has the `heading` key. That way the **intro** heading component is being referred to as `intro` so there is no mixup with the attribute values between the two heading blocks. This can be seen when setting the default attributes:\\n\\n```json\\n\\"cardIntroSize\\": {\\n\\t\\"type\\": \\"string\\",\\n\\t\\"default\\": \\"tiny\\"\\n},\\n\\"cardHeadingSize\\": {\\n\\t\\"type\\": \\"string\\",\\n\\t\\"default\\": \\"big\\"\\n}\\n```\\n\\n## The \\"props\\" Helper\\nThis method does all the heavy lifting for us. It replaces the default attribute names with the ones we provide. There is both the PHP and JS version of it.\\n\\n```php\\nComponents::render(\'heading\', Components::props(\'intro\', $attributes, [\\n\\t\'selectorClass\' => \'intro\',\\n\\t\'blockClass\' => $componentClass\\n]))\\n```\\n\\n```js\\n\\n\\n\\n```\\n\\nIf you `var_dump` the props helper, you\u2019ll notice the `prefix` key, which is built from the names of the blocks and components used hierarchically. For example, when looking at a regular Heading component in the Card block, the prefix will be:\\n\\n`cardCardHeading` - first is the Card block name, then the Card component name and finally Heading component name.\\n\\nThe Heading component that is called Intro then has the prefix `cardCardIntro`. This prefix is then added to the attribute name, which then finally results in `cardCardHeadingSize` and `cardCardIntroSize` attributes, for example.\\n\\nWhile at first glance it seems strange to have this naming scheme, it actually allows us to figure out the hierarchy just by looking at the attribute name.\\n\\n## A step-by-step example\\n\\nThe block we\'ll be creating as an example will be a block which we can use for some sort of comparison or listing pros and cons. To create it, we will need the following:\\n- two `Heading` components\\n- two `List` components\\n\\nThe easiest way to create a new block is by using the boilerplate command\\n\\n`wp boilerplate blocks use-block --name=example`\\n\\nOnce the new block is added to your project, rename it. Also, don\u2019t forget to update all file names and imports in JS.\\n\\nWe should start with `manifest.json`, where we define the components we\'ll use and set the default attributes. For now, just define the components and their keys:\\n\\n```json\\n\\"components\\": {\\n\\t\\"heading\\": \\"heading\\",\\n\\t\\"lists\\": \\"lists\\",\\n\\t\\"secondaryHeading\\": \\"heading\\",\\n\\t\\"secondaryLists\\": \\"lists\\"\\n}\\n```\\n\\nThe first section has the default key names, while the \\"duplicates\\" have different key names.\\n\\nAfter manifest, we can move to the JS part. As this is a fairly simple block without any advanced options or layouts, we need to add two `HeadingEditor` components and two `ListsEditor` components. To make styling easier, we can separate them in two `div` elements. When you\u2019re finished, your code should look like this:\\n\\n```jsx\\nimport React from \'react\';\\nimport { __ } from \'@wordpress/i18n\';\\nimport { HeadingEditor } from \'../../../components/heading/components/heading-editor\';\\nimport { ListsEditor } from \'../../../components/lists/components/lists-editor\';\\n\\nimport { props, selector } from \'@eightshift/frontend-libs/scripts\';\\n\\nexport const ComparisonEditor = ({ attributes, setAttributes }) => {\\n\\tconst {\\n\\t\\tblockClass,\\n\\t} = attributes;\\n\\n\\tconst comparisonPrimaryClass = selector(blockClass, blockClass, \'primary\');\\n\\n\\tconst comparisonSecondaryClass = selector(blockClass, blockClass, \'secondary\');\\n\\n\\treturn (\\n\\t\\t
\\n\\t\\t\\t
\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\t\\t\\t
\\n\\t\\t\\t
\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\t\\t\\t
\\n\\t\\t
\\n\\t);\\n};\\n```\\n\\nNote how we defined the different names with the `props` helper. Additionally, we set the selector class for easier targeting of components when styling.\\n\\nNext, we can add the options. Again, the `props` helper does all the heavy lifting for us. This is how the code should look after adding all component options:\\n\\n```jsx\\nimport React from \'react\';\\nimport { __ } from \'@wordpress/i18n\';\\nimport { props, getOptions } from \'@eightshift/frontend-libs/scripts\';\\nimport { HeadingOptions } from \'../../../components/heading/components/heading-options\';\\nimport { ListsOptions } from \'../../../components/lists/components/lists-options\';\\nimport { PanelBody } from \'@wordpress/components\';\\nimport manifest from \'../manifest.json\';\\n\\nexport const ComparisonOptions = ({ attributes, setAttributes }) => {\\n\\treturn (\\n\\t\\t\\n\\t\\t\\t\\n\\n\\t\\t\\t\\n\\n\\t\\t\\t\\n\\n\\t\\t\\t\\n\\t\\t\\n\\t);\\n};\\n```\\n\\nAdding the PHP part should be simple, but here is the code for reference:\\n\\n```php\\n\\n\\n
\\">\\n\\t\\n\\t
\\">\\n\\t\\t\\n\\t
\\n\\t
\\">\\n\\t\\t\\n\\t
\\n
\\n```\\n\\nThe block should work properly now, but the two lists look the same. To make a difference between the two, we can change the default list colors in the manifest. If we don\u2019t have the colors we want already available in the project, first we need to add them to the global manifest.\\n\\nFor a detailed explanation on how to add new colors to your project, you can read a previous blog post about [modifying blocks](/blog/modifying-blocks-color-theme#adding-new-colors-to-your-project).\\n\\nNow we have to add these new colors to the Lists component `manifest.json`. In `options` key, find the `listsColor` and add your new colors.\\n\\nWhen the new colors are added to the Lists component, we can set these new colors as defaults by adding the following attributes in `manifest.json` of our Comparison block:\\n\\n```json\\n\\"comparisonListsColor\\": {\\n\\t\\"type\\": \\"string\\",\\n\\t\\"default\\": \\"green-haze\\"\\n},\\n\\"comparisonSecondaryListsColor\\": {\\n\\t\\"type\\": \\"string\\",\\n\\t\\"default\\": \\"milano-red\\"\\n}\\n```\\n\\nThe first list will now have green bullet points, and the second one will have red bullet points.\\n\\n![Comparison block](/img/blog/comparison-block.webp)\\n\\nNotice again how the attribute name is structured - current block name (**comparison**), component name (**Lists** or **SecondaryLists**), attribute (**Color**).\\n\\nIf you\u2019re ever in doubt of what is the exact attribute name, you can always `var_dump` the `props` helper for that component and you will see the full attribute names as the keys.\\n\\n```\\nArray\\n(\\n [prefix] => comparisonSecondaryLists\\n // ...\\n [comparisonSecondaryListsOrdered] => ul\\n [comparisonSecondaryListsSize] => body:regular\\n [comparisonSecondaryListsColor] => milano-red\\n [comparisonSecondaryListsColorOnlyMarker] =>\\n [comparisonSecondaryListsUse] => 1\\n // ...\\n)\\n```\\n\\n### Conclusion\\n\\nAlthough this was a simple example, we covered the most important things to have in mind when using multiple same components in a block. For additional practice, you can add some more attributes or go through some of our pre-made blocks which use multiple same components. Some components/blocks you can look into are `Card` and `Quote`."},{"id":"block-patterns","metadata":{"permalink":"/blog/block-patterns","source":"@site/blog/2022-12-22-block-patterns.md","title":"Block Patterns","description":"Intro to block patterns and examples how to use them","date":"2023-03-01T00:00:00.000Z","formattedDate":"March 1, 2023","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"block","permalink":"/blog/tags/block"},{"label":"patterns","permalink":"/blog/tags/patterns"}],"readingTime":3.95,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Block Patterns","description":"Intro to block patterns and examples how to use them","slug":"block-patterns","authors":"obradovic","date":"2023-03-01T00:00:00.000Z","tags":["eightshift","boilerplate","block","patterns"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Using multiple same components","permalink":"/blog/multiple-same-components"},"nextItem":{"title":"Using Custom Post Types and Taxonomies","permalink":"/blog/using-cpts-and-taxonomies"}},"content":"Although Block Patterns may be similar to Block Variations, there are some differences between the two. This blog post will cover what Block Patterns are and how to use them.\\n\x3c!--truncate--\x3e\\n\\n## What are Block Patterns and why use them?\\nBlock Patterns are predefined block layouts which allow you to add content faster and make it look more consistent. The basic idea is to have a ready-made template which consists of multiple blocks, that have options predefined, and all you have to do is add content to these blocks.\\n\\nThey can be used in multiple ways. You can create sections composed of few blocks which you can then easily insert anywhere on the site. You can also create complex page layouts which could be used for writing blog posts, for example.\\n\\n## Technical differences\\nAs explained in the [Eightshift Development kit documentation](https://eightshift.com/docs/basics/blocks-patterns), the main difference between variations and patterns are:\\n- variations provide data using `manifest.json`, while patterns are registered using PHP\\n- variations appear in the editor in the same tab as blocks, while patterns appear in their own tab\\n\\n\\n## Registering Block Patterns\\nTo make registering new block patterns easier, there is a WP-CLI command which will help you create a new block pattern. For this example, the block pattern we\'re making is called **Intro**. To get started, run this WP-CLI command:\\n```shell\\nwp boilerplate blocks create-block-pattern --title=intro\\n```\\n\\nAfter generating the block pattern class, there are a few more adjustments you should make to it. For starters, you should update these methods:\\n- `getName()` - change the return value to `eightshift-boilerplate/intro-pattern`\\n- `getDescription()` - add any description you like\\n\\nOnce these values are updated, you can make the layout you\'ll use as a Block Pattern. The easiest way to do it is to build your layout in the editor, switch to `Code Editor`, copy the code and paste in inside your `getContent()` method. Your pattern should now be available to use. For this example, we can create a simple pattern which consists of a Heading block and a Paragraph block.\\n\\n![Option to switch to Code Editor](/img/blog/code-editor.webp)\\n\\n```php\\nprotected function getContent(): string\\n{\\n\\treturn \'\x3c!-- wp:eightshift-boilerplate/heading {\\"headingHeadingContent\\":\\"Post title goes here\\",\\"headingHeadingSize\\":\\"big\\"} /--\x3e\\n\\t\x3c!-- wp:eightshift-boilerplate/paragraph {\\"paragraphParagraphContent\\":\\"\\\\u003cem\\\\u003ePost intro goes here\\\\u003c/em\\\\u003e\\"} /--\x3e\';\\n}\\n```\\n\\n### Managing Pattern Categories\\n\\nTo make managing block patterns easier, we can group them into categories. To start, we need a service class. This class will be used to register our custom categories and remove core categories. The fastest way to add it to our project is by using the following WP-CLI command:\\n```shell\\nwp boilerplate create service-example --folder=BlockPatterns --file_name=ManagePatternCategories\\n```\\n\\nNow that we have our service class, we\'ll add methods for removing core patterns and pattern categories. First, the method for removing core categories.\\n```php\\n/**\\n * Unregisters core block pattern categories.\\n *\\n * @return void\\n */\\npublic function removeCoreCategories()\\n{\\n\\t\\\\unregister_block_pattern_category(\'buttons\');\\n\\t\\\\unregister_block_pattern_category(\'columns\');\\n\\t\\\\unregister_block_pattern_category(\'gallery\');\\n\\t\\\\unregister_block_pattern_category(\'header\');\\n\\t\\\\unregister_block_pattern_category(\'text\');\\n}\\n```\\nThe second method will remove all core patterns\\n\\n```php\\n/**\\n * Removes support for core block patterns.\\n *\\n * @return void\\n */\\npublic function removeBlockPatternsCore()\\n{\\n\\t\\\\remove_theme_support(\'core-block-patterns\');\\n}\\n```\\n\\nFor these methods to work, we need to add them to the `register()` method of our service class. Add the following actions:\\n```php\\n\\\\add_action(\'init\', [$this, \'removeCoreCategories\'], 40);\\n\\\\add_action(\'after_setup_theme\', [$this, \'removeBlockPatternsCore\'], 20);\\n```\\n\\nIf you try searching for patterns in your editor, you\'ll notice they are no longer available. Now we can move on to adding our block pattern categories. The best approach is to define the pattern category names as constants inside your class. In this example, we will add two categories: **Templates** and **Sections**. We can add the following code inside our class:\\n```php\\nclass ManagePatternCategories implements ServiceInterface\\n{\\n\\tpublic const TEMPLATES_CATEGORY = \'templates-category\';\\n\\tpublic const SECTIONS_CATEGORY = \'sections-category\';\\n\\n\\t// Removed parts of code for better readability.\\n\\n\\t/**\\n\\t * Registers new pattern categories.\\n\\t *\\n\\t * @return void\\n\\t */\\n\\tpublic function addCategories()\\n\\t{\\n\\t\\t\\\\register_block_pattern_category(self::TEMPLATES_CATEGORY, [\\n\\t\\t\\t\'label\' => \\\\esc_html__(\'Templates\', \'es-blog\'),\\n\\t\\t]);\\n\\n\\t\\t\\\\register_block_pattern_category(self::SECTIONS_CATEGORY, [\\n\\t\\t\\t\'label\' => \\\\esc_html__(\'Sections\', \'es-blog\'),\\n\\t\\t]);\\n\\t}\\n}\\n```\\n\\nFinally, return to your Intro Pattern class and update the `getCategories()` method:\\n```php\\nprotected function getCategories(): array\\n{\\n\\treturn [\\n\\t\\tManagePatternCategories::SECTIONS_CATEGORY\\n\\t];\\n}\\n```\\n\\nIn editor, in the Pattern tab, you should now see your new pattern category and the Intro Pattern.\\n\\n![New pattern category and block pattern](/img/blog/block-pattern-example.webp)\\n\\n## Things to keep in mind\\nBlock patterns add layout and content which you can then modify at will per instance. These instances are not synchronised with the codebase so it becomes an issue if you\'ve already used a pattern on multiple places on the site and then notice you\'ve missed something in the pattern configuration phase. Fixing the pattern in the codebase will apply the changes on all newly added instances, but you will have to fix the misconfigured instances manually."},{"id":"using-cpts-and-taxonomies","metadata":{"permalink":"/blog/using-cpts-and-taxonomies","source":"@site/blog/2022-12-13-using-cpts-and-taxonomies.md","title":"Using Custom Post Types and Taxonomies","description":"How to register and use custom post types and taxonomies with Eightshift Dev Kit","date":"2022-12-13T00:00:00.000Z","formattedDate":"December 13, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"cpt","permalink":"/blog/tags/cpt"},{"label":"custom post type","permalink":"/blog/tags/custom-post-type"},{"label":"taxonomy","permalink":"/blog/tags/taxonomy"},{"label":"taxonomies","permalink":"/blog/tags/taxonomies"},{"label":"terms","permalink":"/blog/tags/terms"}],"readingTime":4.115,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Using Custom Post Types and Taxonomies","description":"How to register and use custom post types and taxonomies with Eightshift Dev Kit","slug":"using-cpts-and-taxonomies","authors":"obradovic","date":"2022-12-13T00:00:00.000Z","tags":["eightshift","boilerplate","cpt","custom post type","taxonomy","taxonomies","terms"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Block Patterns","permalink":"/blog/block-patterns"},"nextItem":{"title":"Block Variations","permalink":"/blog/block-variations"}},"content":"WordPress offers two default ways to group content by content type - using posts or pages. Posts and pages are registered as default post types in the WordPress app. But sometimes, that is not enough. That\'s where custom post types (CPT) and custom taxonomies come in. This blog post will cover the basics of registering CPTs and taxonomies using Eightshift Dev Kit.\\n\\n\x3c!--truncate--\x3e\\n## Why should you use CPTs?\\nThe great thing about WordPress is how extensible it is. With projects becoming more complex, the need for additional post types and taxonomies increases. Sure, you can cram everything into default posts or pages, but this can become very chaotic. Using custom post types allows you to manage your content much better. You can separate events and projects from news articles for instance.\\n\\nPost `category` and `tag` are taxonomies - a grouping you can further separate by terms belonging to that taxonomy. WordPress, since version 2.3.0 offers a way to register your own, custom taxonomies.\\n\\n## Registering Custom Post Types\\nFor this exercise, we\'ll create a new post type called `Projects`. To make the process of registering new CPTs as easy as possible, we\'ll use a WP-CLI command to create our CPT with the following command:\\n\\n```bash\\nwp boilerplate create post-type --label=\'Project\' --plural_label=\'Projects\' --slug=\'project\' --rewrite_url=\'project\' --rest_endpoint_slug=\'projects\'\\n```\\n\\n:::tip\\nIf your new CPT is not working, try flushing rewrite rules by re-saving the settings in **Settings -> Permalinks** or by using `wp cache flush` CLI command\\n:::\\n\\nYour new post type is registered and ready to use! Easy, right?\\n\\nSometimes all these parameters can be a bit confusing, so here\'s a quick reference of best practices when setting these parameters:\\n\\n| Parameter | Singular/Plural | Writing style | Example |\\n|--------------------|-----------------|---------------|----------|\\n| label | Singular | Regular | Project |\\n| plural_label | Plural | Regular | Projects |\\n| slug | Singular | kebab-case | project |\\n| rewrite_url | Singular | kebab-case | project |\\n| rest_endpoint_slug | Plural | kebab-case | projects |\\n\\nThere is a reason for this naming convention. For example:\\n- `slug` is attached to a single custom post type in the database, which is why it is written in singular\\n- `rest_endpoint_slug` is used to fetch a collection of posts from that custom post type, which is why it should be written in plural\\n\\n## Registering Taxonomies\\nNow that we have the new custom post type, we need a way to group the projects. We\'ll create a custom taxonomy called `Project Technology`. As with the CPT registration, the easiest way to register taxonomies is by using the following WP-CLI command:\\n\\n```bash\\nwp boilerplate create taxonomy --label=\'Project Technology\' --plural_label=\'Project Technologies\' --slug=\'project-technology\' --rest_endpoint_slug=\'project-technologies\' --post_type_slug=\'project\'\\n```\\n\\nSimilar suggestions apply to the parameters when naming taxonomies as well. Be sure to write the correct post type slug for which you are registering this new taxonomy!\\n\\n## Structure\\nWhen checking your codebase after adding these new custom post types and taxonomies, you\'ll notice the post types are located inside the `src/CustomPostType` folder, and the taxonomies are located inside the `src/CustomTaxonomy` folder. Following the **Single Responsibility Principle**, each post type or taxonomy is in a separate class.\\n\\n## Modifying options\\nOur custom post type and taxonomy are ready to use, but we still want to make some changes. For starters, we want another icon and for the Projects to be located below the Posts in the WordPress admin menu. In `src/CustomPostType/ProjectPostType.php`, find the `MENU_POSITION` constant and change it to `5`. The lower the number, the higher it will be in the menu.\\n\\nNext, we want to update the icon representing the new post type in the menu. These icons are named dashicons. Change the `MENU_ICON` constant to `dashicons-clipboard`. If you want another icon for your CPT, here is the list of [available dashicons](https://developer.wordpress.org/resource/dashicons/). Finally, we want to remove the author and comments. In `getPostTypeArguments()` method, find the key `supports` in the return value and remove `author` and `comments` from the array.\\n\\n## Clashing slugs\\nSomething that can happen when working on your project is that you have the same slugs for your custom post type and your page, for example. Let\'s say you have a page with a slug `project`, and a custom post type with a slug `project`. When trying to access the page, you\'ll keep getting the Project CPT archive. To fix it, you have to change one of the slugs or write a custom redirection rule (which we don\'t recommend, as the redirections are tricky to handle in WordPress).\\n\\n## Further reading\\nFor the best overview of all the options you have when registering your custom post type or taxonomy, we recommend checking the official WordPress docs for the [register_post_type()](https://developer.wordpress.org/reference/functions/register_post_type/) and [register_taxonomy()](https://developer.wordpress.org/reference/functions/register_taxonomy/) functions.\\n\\nIf you would like to know more about taxonomies and terms, along with how they are stored in the database, you can read more about it in the [Categories, Tags, & Custom Taxonomies](https://developer.wordpress.org/themes/basics/categories-tags-custom-taxonomies/) page of the WordPress docs."},{"id":"block-variations","metadata":{"permalink":"/blog/block-variations","source":"@site/blog/2022-09-07-block-variations.md","title":"Block Variations","description":"Intro to block variations and examples of how to use them","date":"2022-09-07T00:00:00.000Z","formattedDate":"September 7, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"block","permalink":"/blog/tags/block"},{"label":"variations","permalink":"/blog/tags/variations"}],"readingTime":3.035,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Block Variations","description":"Intro to block variations and examples of how to use them","slug":"block-variations","authors":"obradovic","date":"2022-09-07T00:00:00.000Z","tags":["eightshift","boilerplate","block","variations"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Using Custom Post Types and Taxonomies","permalink":"/blog/using-cpts-and-taxonomies"},"nextItem":{"title":"How to use ACF in your project","permalink":"/blog/acf-in-a-project"}},"content":"Let\'s picture the following scenario: You just created a block with many options and now you want multiple versions of that block available with pre-set options. That\'s where variations come in handy!\\n\x3c!--truncate--\x3e\\n\\n## What are variations?\\n\\nBlock variations allow us to override default block attributes. We can select a variation from the block list with all preset options instead of manually setting them.\\n\\n:::note\\nYou cannot add new attributes in variations. Only attributes that exist in the parent block can be used.\\n:::\\n\\nFor example, we have a `Card` block. If we want to use it for something like downloading PDF files, we may not need image or paragraph components. A simple text that describes type of the file, title of the file and a download button are all we need in this case.\\n\\nOur new card should consist of the following components:\\n- intro\\n- heading\\n- button\\n\\n## How to register a block variation?\\n\\nThe process of registering block variations is fairly simple. In order to register a block variation, go to **src/Blocks/variations**, add a new folder, and let\'s call it `card-resource`. Inside that folder, all you need to do is add a `manifest.json` file. It is also recommended to add a `docs` folder in which you can add a readme file and storybook file.\\n\\nInside your `manifest.json` file, add the attributes to define the new default attributes for this variation. Here is an example:\\n\\n```json\\n{\\n\\t\\"$schema\\": \\"https://raw.githubusercontent.com/infinum/eightshift-frontend-libs/develop/schemas/variation.json\\",\\n\\t\\"parentName\\": \\"card\\",\\n\\t\\"name\\": \\"card-resource\\",\\n\\t\\"title\\": \\"Card Resource\\",\\n\\t\\"description\\" : \\"Card variation without image and paragraph\\",\\n\\t\\"icon\\": {\\n\\t\\t\\"src\\": \\"es-card\\"\\n\\t},\\n\\t\\"attributes\\": {\\n\\t\\t\\"cardCardImageUse\\": false,\\n\\t\\t\\"cardCardParagraphUse\\": false,\\n\\t\\t\\"cardCardHeadingSize\\": \\"regular\\",\\n\\t\\t\\"cardCardButtonColor\\": \\"blue\\"\\n\\t},\\n\\t\\"scope\\": [\\n\\t\\t\\"inserter\\"\\n\\t]\\n}\\n```\\n\\nAfter adding this code, your new block variation should now be visible in the block list. It\'s that easy!\\n\\n![Variation of the Card block](/img/blog/card-resource.webp)\\n\\n## Providing inner block data\\nOther than overriding default attributes with variations, you can do much more with Eightshift Development kit. If you have a block that uses inner blocks, you can even provide inner block data.\\n\\nThe following example is very basic, but it will give you an idea of how to provide inner block data. It can easily be reproduced with our `Carousel` block if you want to try it yourself.\\n\\n:::note\\nIf you don\'t have Carousel block in your project yet, you can add it with the following WP-CLI command: `wp boilerplate blocks use-block --name=\\"carousel\\"`\\n:::\\n\\nOnce you have the `Carousel` block up and running, create a variation called `Carousel Loop`. For this variation, we want the following:\\n- loop\\n- pagination\\n- two images per slide\\n\\nWe will also add some placeholder images so you can immediately test the Carousel block variation. Here is the code you can add to the `manifest.json` file for that variation.\\n```json\\n{\\n\\t\\"$schema\\": \\"https://raw.githubusercontent.com/infinum/eightshift-frontend-libs/develop/schemas/variation.json\\",\\n\\t\\"parentName\\": \\"carousel\\",\\n\\t\\"name\\": \\"carousel-loop\\",\\n\\t\\"title\\": \\"Carousel Loop\\",\\n\\t\\"description\\" : \\"Carousel block variation with loop and pagination enabled, along with multiple image blocks with placeholders. Displays two images per slide.\\",\\n\\t\\"icon\\": {\\n\\t\\t\\"src\\": \\"es-card\\"\\n\\t},\\n\\t\\"attributes\\": {\\n\\t\\t\\"carouselIsLoop\\": true,\\n\\t\\t\\"carouselShowPagination\\": true,\\n\\t\\t\\"carouselShowItems\\": 2\\n\\t},\\n\\t\\"innerBlocks\\": [\\n\\t\\t{\\n\\t\\t\\t\\"name\\": \\"eightshift-boilerplate/image\\",\\n\\t\\t\\t\\"attributes\\": {\\n\\t\\t\\t\\t\\"imageImageFull\\":true,\\n\\t\\t\\t\\t\\"imageImageUrl\\": \\"https://loremflickr.com/400/400\\"\\n\\t\\t\\t}\\n\\t\\t},\\n\\t\\t{\\n\\t\\t\\t\\"name\\": \\"eightshift-boilerplate/image\\",\\n\\t\\t\\t\\"attributes\\": {\\n\\t\\t\\t\\t\\"imageImageFull\\":true,\\n\\t\\t\\t\\t\\"imageImageUrl\\": \\"https://loremflickr.com/400/400\\"\\n\\t\\t\\t}\\n\\t\\t},\\n\\t\\t{\\n\\t\\t\\t\\"name\\": \\"eightshift-boilerplate/image\\",\\n\\t\\t\\t\\"attributes\\": {\\n\\t\\t\\t\\t\\"imageImageFull\\":true,\\n\\t\\t\\t\\t\\"imageImageUrl\\": \\"https://loremflickr.com/400/400\\"\\n\\t\\t\\t}\\n\\t\\t},\\n\\t\\t{\\n\\t\\t\\t\\"name\\": \\"eightshift-boilerplate/image\\",\\n\\t\\t\\t\\"attributes\\": {\\n\\t\\t\\t\\t\\"imageImageFull\\":true,\\n\\t\\t\\t\\t\\"imageImageUrl\\": \\"https://loremflickr.com/400/400\\"\\n\\t\\t\\t}\\n\\t\\t}\\n\\t],\\n\\t\\"scope\\": [\\n\\t\\t\\"inserter\\"\\n\\t]\\n}\\n```\\n\\nAfter adding this code, you should see the `Carousel Loop` variation in your block list. After adding it in the editor, the `Carousel` attributes will be already set to the ones you provided, as well as two images that are added as inner blocks.\\n\\n![Block list with variations](/img/blog/block-list-variations.webp)"},{"id":"acf-in-a-project","metadata":{"permalink":"/blog/acf-in-a-project","source":"@site/blog/2022-05-10-acf-in-a-project.md","title":"How to use ACF in your project","description":"Example of using ACF plugin in your project","date":"2022-05-17T00:00:00.000Z","formattedDate":"May 17, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"acf","permalink":"/blog/tags/acf"},{"label":"advanced custom fields","permalink":"/blog/tags/advanced-custom-fields"},{"label":"theme options","permalink":"/blog/tags/theme-options"}],"readingTime":5.485,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"How to use ACF in your project","description":"Example of using ACF plugin in your project","slug":"acf-in-a-project","authors":"obradovic","date":"2022-05-17T00:00:00.000Z","tags":["eightshift","boilerplate","acf","advanced custom fields","theme options"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Block Variations","permalink":"/blog/block-variations"},"nextItem":{"title":"Using assets in a project","permalink":"/blog/using-assets"}},"content":"If you\'ve worked with WordPress for a long time, you\'ve heard of the **_Advanced Custom Fields_** plugin (**_ACF_** for short). While the use of blocks has simplified content editing, thus making meta fields less of a necessity, there are still cases in which meta fields are very useful.\\n\x3c!--truncate--\x3e\\n\\n## Ways of registering ACF fields\\n\\nThere are multiple approaches to ACF field registration. The easiest way to register fields is using the WP admin interface, but this approach has a few drawbacks. If you have many fields, this can impact performance because you are registering fields dynamically (information about registered fields is stored in the database). Another drawback is if you have multiple environments (e.g. local, staging, production), you\'ll have to export the fields and import them to other environments.\\n\\nOther ways of registering ACF fields are either via PHP (code) or by reading from a JSON file. We prefer to use PHP because you can use OPcache to speed up field registration even more. If you are using Git, this approach is even better because you can commit the PHP code used for registering fields for easier portability across different environments. For that reason, Eightshift Development kit already has some goodies which will make field registration a breeze.\\n\\n## Exporting PHP code\\n\\nThe approach we use for registering ACF fields is by adding those fields first through the WP Admin ACF interface. As an example, We\'ll add a field group called **_Intro_**, which will contain one text field of the same name. That field group will be displayed only on posts. You can add a few more fields, but for the sake of simplicity, we\'ll only use one field.\\n\\nWhen you define all the fields you need, save them and go to `Custom Fields -> Tools`. Here you will see an option to **_Export Field Groups_**. Simply select the field groups which you want to export and select **_Generate PHP_**.\\n\\n![ACF PHP code export](/img/blog/acf-generate-php.webp)\\n\\nThis will generate a PHP code snippet that you can use in your theme. Now you may be wondering, where exactly should that code go?\\n\\n## CustomMeta class\\n\\nThose ACF goodies in Eightshift Development kit we talked about earlier? Let us introduce you to one of them. We have a WP CLI command which we can use to generate a CustomMeta class where we can add our field groups. The command is `wp boilerplate create acf-meta`. This command has one required parameter, and that is `name`. To create a class that we will use for registering our custom fields, we\'ll use the following command:\\n\\n```bash\\nwp boilerplate create acf-meta --name=intro\\n```\\n\\nThis command will generate a **_CustomMeta_** folder inside **_src_** folder and add a new file called **_IntroAcfMeta.php_**. Inside that file, you should see the following method:\\n```php\\npublic function fields(): void\\n{\\n\\tif (function_exists(\'acf_add_local_field_group\')) {\\n\\t\\t\\\\acf_add_local_field_group([]);\\n\\t}\\n}\\n```\\n\\nGo back to your PHP code export for ACF fields and select everything inside the `acf_add_local_field_group()` function and paste it into your function. To make it in line with our coding standards, we have to do the following:\\n- replace `array()` with `[]`\\n- make every label translatable and escaped - use `esc_html__()`\\n- instead of hardcoding the field name, replace it with a class constant\\n\\nAfter all these changes, your code should look like this:\\n\\n```php\\nclass IntroAcfMeta extends AbstractAcfMeta\\n{\\n\\n\\t/**\\n\\t * Intro field name.\\n\\t */\\n\\tpublic const INTRO_FIELD_NAME = \'intro\';\\n\\n\\t/**\\n\\t * Render acf fields.\\n\\t *\\n\\t * @return void\\n\\t */\\n\\tpublic function fields(): void\\n\\t{\\n\\t\\tif (function_exists(\'acf_add_local_field_group\')) {\\n\\t\\t\\t\\\\acf_add_local_field_group([\\n\\t\\t\\t\\t\'key\' => \'group_6269300acefda\',\\n\\t\\t\\t\\t\'title\' => \\\\esc_html__(\'Intro\', \'eightshift-theme\'),\\n\\t\\t\\t\\t\'fields\' => [\\n\\t\\t\\t\\t\\t[\\n\\t\\t\\t\\t\\t\\t\'key\' => \'field_6269300f8029b\',\\n\\t\\t\\t\\t\\t\\t\'label\' => \\\\esc_html__(\'Intro\', \'eightshift-theme\'),\\n\\t\\t\\t\\t\\t\\t\'name\' => self::INTRO_FIELD_NAME,\\n\\t\\t\\t\\t\\t\\t\'type\' => \'text\',\\n\\t\\t\\t\\t\\t\\t// ...\\n\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t]\\n\\t\\t\\t\\t// ...\\n\\t\\t\\t]);\\n\\t\\t}\\n\\t}\\n}\\n```\\n\\nThe final step is to go back to the Custom Fields in WP admin and either delete or deactivate your field group from there, to prevent registering the fields twice. After adding field definitions in PHP and removing them in WP admin, your field should be visible when editing posts.\\n\\n## Using get_field()\\n\\nTo fetch the saved meta value, we use ACF\'s `get_field()` function, but here are a few tips that could improve your code quality. First, you should check if that function exists. That way, if ACF is deactivated on your site for whatever reason, your site won\'t break. The second tip is to use a class constant instead of hardcoding the field name. With these practices in mind, your code should look like this:\\n\\n```php\\nuse YourNamespace\\\\CustomMeta\\\\IntroAcfMeta;\\n\\n// ...\\n\\nif (function_exists(\'get_field\')) {\\n\\t$introText = get_field(IntroAcfMeta::INTRO_FIELD_NAME, $postId);\\n}\\n```\\n\\n:::tip\\nIt\'s better to use class constants because if you decide to change the field name, you will have to change it only in one place.\\n:::\\n\\n## Theme Options\\n\\nACF\'s Options page has a wide array of uses and it\'s very likely that you\'ll need some sort of Theme Options in your project. To make the implementation of Theme Options a bit easier, we have a CLI command which generates the `ThemeOptions` class in your project. Just use the following command:\\n\\n```bash\\nwp boilerplate create theme-options\\n```\\n\\nThis command generates a class with two methods. The first one, `createThemeOptionsPage()` creates a Theme Options page and adds it to the WP Admin sidebar. The second one, `registerThemeOptions()`, is what registers the fields you will have in Theme Options. Here is an example how Theme Options look after being created using `wp boilerplate`:\\n\\n![ACF Theme Options](/img/blog/acf-theme-options.webp)\\n\\nTo add fields to your Theme Options, follow the steps from the **_Exporting PHP code_** section of this post and add the fields inside the `\'fields\' => []` array.\\n## Tip - create a helper class\\n\\nIn this blog post, we covered the whole process of registering and using ACF fields in your project. But, if you are using a lot of ACF fields, wrapping each `get_field()` function with a `function_exists()` conditional may become tedious at some point. For that reason, it may be a good idea to create a helper class that you can use for ACF functions.\\n\\nI won\'t cover the whole process in detail, but I\'ll give you some general pointers.\\n\\n- when registering plugin-related classes, use the `Plugins` namespace. In this case, you should have namespace `YourNamespace\\\\Plugins\\\\Acf`\\n- inside that namespace, you can create a class called `AcfHelper`\\n- add a method `getField` which accepts the same arguments as the `get_field()` function\\n- add a method `getThemeOption` which only accepts one argument, the field name, and the second argument is hardcoded\\n- use early returns in your methods\\n```php\\nif (!function_exists(\'get_field\')) {\\n\\treturn \'\';\\n}\\n```"},{"id":"using-assets","metadata":{"permalink":"/blog/using-assets","source":"@site/blog/2022-04-25-using-assets.md","title":"Using assets in a project","description":"Step-by-step guide on how to add assets like images or icons to your theme.","date":"2022-04-28T00:00:00.000Z","formattedDate":"April 28, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"assets","permalink":"/blog/tags/assets"},{"label":"images","permalink":"/blog/tags/images"},{"label":"icons","permalink":"/blog/tags/icons"}],"readingTime":3.935,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Using assets in a project","description":"Step-by-step guide on how to add assets like images or icons to your theme.","slug":"using-assets","authors":"obradovic","date":"2022-04-28T00:00:00.000Z","tags":["eightshift","boilerplate","assets","images","icons"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"How to use ACF in your project","permalink":"/blog/acf-in-a-project"},"nextItem":{"title":"Adding fonts","permalink":"/blog/adding-fonts"}},"content":"Previously, we went through the process of adding fonts to your project. While the process of adding additional assets like images or icons has some similarities to adding fonts, it also has its unique steps. In this post, we\'ll cover multiple ways of adding assets and using them on your site.\\n\x3c!--truncate--\x3e\\n\\n## Adding images\\n\\nSimilar to fonts, there is also a dedicated folder for adding images that will be used in a theme. The location of this folder is **_assets/images_**. The procedure to add images is even simpler than fonts, which you can read about in [Adding fonts](/blog/adding-fonts) blog post. Just follow these steps to add a new image:\\n- add the image in inside **_assets/images_** folder\\n- include it in **_assets/images/index.js_**\\n- run `npm start` to rebuild assets\\n\\nThe new image will now be available in the **_public_** folder. The use case for this is adding a favicon or a logo to your project. To use this image in one of your templates or blocks, you have to add the following in your **_php_** file:\\n\\n```php\\nuse YourNamespace\\\\Manifest\\\\Manifest;\\n// ...\\n\\napply_filters(Manifest::MANIFEST_ITEM, \'logo.svg\');\\n```\\n\\nThe filter we are using is called `manifest-item` and we use it to get the URL of the asset from the **_public_** folder. You can read more about this in [our documentation](/docs/basics/manifest).\\n\\nYou can see how this is being used for rendering both favicon and header logo in your theme\'s **_header.php_** file.\\n\\n:::tip\\nDon\'t hardcode the filter name in the `apply_filters` function. Always call it via class constants.\\n:::\\n\\nFor better organization, you can add additional folders (e.g. **_icons_**, **_placeholders_**) inside the **_assets/images_** folder. Here\'s an example of how to include them:\\n\\n```js\\n// Icons\\nimport \'./icons/upload.svg\';\\n\\n// Placeholders\\nimport \'./placeholders/post.png\';\\nimport \'./placeholders/page.png\';\\n```\\n\\n## Using SVG files from manifest\\n\\nIf you recall from a previous blog post about [Modifying Blocks](/blog/modifying-blocks-color-theme), you might have already seen an alternative approach to including SVG files in your block or component.\\n\\n:::info :es-hide-title:\\nIf you don\'t have it in your project, be sure to read our blog post about adding blocks and components by using [WP CLI](/blog/adding-blocks-wpcli).\\n:::\\n\\nOpen **_src/Blocks/components/quote/manifest.json_** and you\'ll see that the icon used by the component is defined inside `resources` as a key-value pair. Key represents the name that we will use to fetch the icon, while the value is SVG code.\\n\\n```json\\n\\"resources\\": {\\n\\t\\"icon\\": \\"...\\"\\n}\\n```\\n\\nIn order to make the minification of SVG files as easy as possible, our teammate Goran made an awesome tool called [SVG2WP](https://svg-2-wp.goranalkovic.com/). Some of the options include making attributes JSX compatible, or replacing the color value with `currentColor`, which can then be used to change the SVG color through CSS.\\n\\nYou\'ve already seen the use of `currentColor` in the above-mentioned blog post, where we\'ve modified the color of the SVG.\\n\\nThe output of the icon on frontend is very simple. In the Quote component, it was done the following way:\\n```php\\n\\n\\n\\">\\n\\t\\n\\n```\\n\\nAn excellent example, where you can see in even more detail how SVGs are being used, is our `icon` component. It isn\'t included in Eightshift theme by default, so you have to add it to your project with WP CLI. To include it in your project, use the following command:\\n\\n```bash\\nwp boilerplate blocks use-component --name=icon\\n```\\n\\nIf you include the Icon component inside a block, you will have the option to choose between multiple icons defined in the manifest. Another way to render SVGs from the Icon component is by using the `Components::render` helper method:\\n\\n```php\\necho Components::render(\\n\\t\'icon\',\\n\\t[\\n\\t\\t\'blockClass\' => $componentClass,\\n\\t\\t\'iconName\' => \'download\',\\n\\t]\\n);\\n```\\n\\nHere are some examples of icons available out-of-the-box in our Icon component:\\n\\n![Icon component](/img/blog/icon-component.webp)\\n\\n## Using icons for editor and block options\\n\\nWhen developing your blocks and adding new options, you may need to add icons to improve the user experience. We have many icons already available for use. You can see the full list in our [Storybook](/storybook) under `Editor -> Icons` section. We already added the icon when adding a new Color Theme option for the Quote block. Here is the simplified version:\\n```jsx\\nimport { ColorPaletteCustom, IconLabel, icons } from \'@eightshift/frontend-libs/scripts\';\\n\\nreturn (\\n\\t}\\n\\t\\t// ...\\n\\t/>\\n);\\n```\\n\\nThis was the end result when we were adding a new option in our Quote block:\\n\\n![Color Theme Options](/img/blog/color-theme-options.webp)\\n\\n## Conclusion\\n\\nAs you could see in this blog post, there are multiple ways of adding assets to a project. It all depends on how these will be used and what the scope of their use will be."},{"id":"adding-fonts","metadata":{"permalink":"/blog/adding-fonts","source":"@site/blog/2022-04-08-adding-fonts.md","title":"Adding fonts","description":"An intro to adding fonts to your project.","date":"2022-04-12T00:00:00.000Z","formattedDate":"April 12, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"fonts","permalink":"/blog/tags/fonts"}],"readingTime":6.005,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Adding fonts","description":"An intro to adding fonts to your project.","slug":"adding-fonts","authors":"obradovic","date":"2022-04-12T00:00:00.000Z","tags":["eightshift","boilerplate","fonts"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Using assets in a project","permalink":"/blog/using-assets"},"nextItem":{"title":"Modifying blocks - Color Theme","permalink":"/blog/modifying-blocks-color-theme"}},"content":"Every project is unique. Logo, colors, fonts, etc. are what define the visual identity of your website. In this post, we\'ll cover adding fonts to a project.\\n\x3c!--truncate--\x3e\\n\\n## Importing fonts into your project\\n[Our documentation](/docs/basics/fonts) covers the necessary steps to add a font to your project, but here we\'ll cover the process in a bit more detail. To start, we need a font (or two). For this example, I\'ll use **_Source Sans Pro_** and **_Noto Serif_** which I\'ve downloaded from [Google Fonts](https://fonts.google.com/). They are in the `.ttf` format (you may find some which are `.otf`, which will work in the same way), so you need to convert them to `.woff` and `.woff2`. Whatever approach you use is okay - we recommend the following tools:\\n- [Convertio](https://convertio.co/ttf-woff/)\\n- [Cloud Convert](https://cloudconvert.com/ttf-to-woff)\\n- [Everything Fonts](https://everythingfonts.com/ttf-to-woff)\\n- [Transfonter](https://transfonter.org/)\\n\\n:::caution Warning\\nMake sure you have the proper license for the fonts you are converting.\\n:::\\n\\nFonts should go inside your theme\'s **_/assets/fonts_** folder. Copy the fonts you want to use there. You will also notice that this folder contains an **_index.js_** file, used to import fonts into your project. Here\'s an example of how I imported my fonts:\\n```js\\n// SourceSansPro WOFF\\nimport \'./SourceSansPro-Bold.woff\';\\nimport \'./SourceSansPro-BoldItalic.woff\';\\nimport \'./SourceSansPro-Italic.woff\';\\nimport \'./SourceSansPro-Regular.woff\';\\nimport \'./SourceSansPro-Light.woff\';\\nimport \'./SourceSansPro-LightItalic.woff\';\\n\\n// SourceSansPro WOFF2\\nimport \'./SourceSansPro-Bold.woff2\';\\nimport \'./SourceSansPro-BoldItalic.woff2\';\\nimport \'./SourceSansPro-Italic.woff2\';\\nimport \'./SourceSansPro-Regular.woff2\';\\nimport \'./SourceSansPro-Light.woff2\';\\nimport \'./SourceSansPro-LightItalic.woff2\';\\n\\n// NotoSerif WOFF\\nimport \'./NotoSerif-Bold.woff\';\\nimport \'./NotoSerif-BoldItalic.woff\';\\nimport \'./NotoSerif-Italic.woff\';\\nimport \'./NotoSerif-Regular.woff\';\\n\\n// NotoSerif WOFF2\\nimport \'./NotoSerif-Bold.woff2\';\\nimport \'./NotoSerif-BoldItalic.woff2\';\\nimport \'./NotoSerif-Italic.woff2\';\\nimport \'./NotoSerif-Regular.woff2\';\\n```\\n\\n:::tip\\nIf you don\'t need to support IE11, don\'t include `.woff` files. This will save you some bandwidth.\\n:::\\n\\nTo add these fonts as your base font and secondary font, go to the global manifest located in **_/src/Blocks_** and add the following inside `globalVariables`:\\n```json\\n\\"globalVariables\\": {\\n\\t// ...\\n\\t\\"baseFont\\": \\"SourceSansPro\\",\\n\\t\\"secondaryFont\\": \\"NotoSerif\\",\\n\\t// ...\\n}\\n```\\n\\nNext, you can create a new file called **__typography.scss_** inside your **_/assets/styles/parts/utils/_** folder and add the following:\\n\\n```scss\\n@include font-face(global-settings(baseFont), \'SourceSansPro-Light\', 300);\\n@include font-face(global-settings(baseFont), \'SourceSansPro-LightItalic\', 300, italic);\\n@include font-face(global-settings(baseFont), \'SourceSansPro-Regular\', 400);\\n@include font-face(global-settings(baseFont), \'SourceSansPro-Italic\', 400, italic);\\n@include font-face(global-settings(baseFont), \'SourceSansPro-Bold\', 700);\\n@include font-face(global-settings(baseFont), \'SourceSansPro-BoldItalic\', 700, italic);\\n\\n@include font-face(global-settings(secondaryFont), \'NotoSerif-Bold\', 700);\\n@include font-face(global-settings(secondaryFont), \'NotoSerif-BoldItalic\', 700, italic);\\n@include font-face(global-settings(secondaryFont), \'NotoSerif-Italic\', 400, italic);\\n@include font-face(global-settings(secondaryFont), \'NotoSerif-Regular\', 400);\\n```\\nIf you would like to know more about the `font-face` mixin, you can take a look at our [Sass documentation](/docs/basics/library).\\n\\nBecause this is a new file, we have to include it. You can do that inside **_/assets/styles/parts/\\\\_shared.scss_** file:\\n```scss\\n// Project specific.\\n@import \'utils/shared-variables\';\\n@import \'utils/typography\';\\n```\\n\\nRun `npm start` to rebuild your **_public_** folder and assets. If you did everything correctly, your build should pass and you will see your fonts inside the **_public_** folder.\\n\\n## Using only one font in a block\\n\\nThere are multiple ways of using fonts in a block. The simplest use case is if you have only one font you want to use for that specific block. In this case, we want the Heading block to only use _Noto Serif_.\\n\\nTo make our secondary font available for use, we need to first define it as a CSS variable. We can do that in **_/assets/styles/parts/utils/\\\\_shared-variables.scss_**. The base font is already defined there, so all we need to do is add our secondary font definition below it.\\n\\nTo make things a bit more consistent, we may also want to rename `--global-font-family` CSS variable to `--base-font-family`. Just don\'t forget to search/replace this new variable name across your project! Please note that the fallbacks for the fonts can be anything, this is just an example. Once we\'re done, it should look like this:\\n```scss\\n\\t--base-font-family: var(--global-base-font), -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Helvetica,\\n\\t\\tArial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\';\\n\\n\\t--secondary-font-family: var(--global-secondary-font), -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Helvetica,\\n\\t\\tArial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\';\\n```\\nAfter defining the `--secondary-font-family` CSS variable, we can go to **_/src/Blocks/components/heading/heading-style.scss_** and add the following rule to the `.heading` class:\\n```scss\\n\\tfont-family: var(--secondary-font-family);\\n```\\n\\nAnd that\'s it! The Heading block will now use the _Noto Serif_ font.\\n\\n## Adding a font picker\\n\\nIn some cases, you may want to give users the option to choose between fonts that they want to use in their block. For this example, we\'ll use the `paragraph` block where we want users to have both _Source Sans Pro_ and _Noto Serif_ available.\\n\\nFirst step is to add a new attribute, options and CSS variable values in **_/src/Blocks/components/paragraph/manifest.json_**:\\n```json\\n// ...\\n\\t\\"attributes\\": {\\n\\t\\t// ...\\n\\t\\t\\"paragraphFontFamily\\": {\\n\\t\\t\\t\\"type\\": \\"string\\",\\n\\t\\t\\t\\"default\\": \\"base\\"\\n\\t\\t}\\n\\t},\\n\\t\\"options\\": {\\n\\t\\t// ...\\n\\t\\t\\"paragraphFontFamily\\": [\\n\\t\\t\\t{\\n\\t\\t\\t\\t\\"label\\": \\"SourceSansPro\\",\\n\\t\\t\\t\\t\\"value\\": \\"base\\"\\n\\t\\t\\t},\\n\\t\\t\\t{\\n\\t\\t\\t\\t\\"label\\": \\"NotoSerif\\",\\n\\t\\t\\t\\t\\"value\\": \\"secondary\\"\\n\\t\\t\\t}\\n\\t\\t],\\n\\t\\t// ...\\n\\t}\\n```\\n\\nAfter defining the new attribute and options for the font family, we now have to add a variable to **_manifest.json_**. We can add it to the `variables` object. This approach is slightly different from the one explained in the [Modifying blocks](/blog/modifying-blocks-color-theme) blog post. Here we can use `%value%` wildcard to dynamically add the selected value to our CSS variable.\\n```json\\n\\"paragraphFontFamily\\": [\\n\\t{\\n\\t\\t\\"variable\\": {\\n\\t\\t\\t\\"paragraph-font-family\\": \\"var(--%value%-font-family)\\"\\n\\t\\t}\\n\\t}\\n]\\n```\\n\\nThe next step is to go to **_/src/Blocks/components/paragraph/components/paragraph-options.js_** and add a control for the new font family option. The first thing we can add is a new attribute that will allow us to toggle showing the Paragraph font family option on other blocks which are using the Paragraph component. There might be a case where we want only one font family, so this option may come in handy in some other blocks.\\n```js\\nconst {\\n\\t\\tsetAttributes,\\n\\t\\t//...\\n\\t\\tshowParagraphFontFamily = true,\\n\\t} = attributes;\\n```\\n\\nAfter that, we need to fetch either the saved attribute value or get the default one from the manifest. We can do that with the `checkAttr` helper, adding it just below `paragraphColor` and `paragraphSize`.\\n```js\\n\\tconst paragraphFontFamily = checkAttr(\'paragraphFontFamily\', attributes, manifest);\\n```\\n\\nNow we have to add an actual control to the options panel which will allow us to choose between fonts.\\n```js\\n\\treturn (\\n\\t\\t// ...\\n\\t\\t{showParagraphFontFamily &&\\n\\t\\t\\t}\\n\\t\\t\\t\\tvalue={paragraphFontFamily}\\n\\t\\t\\t\\toptions={getOption(\'paragraphFontFamily\', attributes, manifest)}\\n\\t\\t\\t\\tonChange={(value) => setAttributes({ [getAttrKey(\'paragraphFontFamily\', attributes, manifest)]: value })}\\n\\t\\t\\t\\tisClearable={false}\\n\\t\\t\\t\\tisSearchable={false}\\n\\t\\t\\t\\tsimpleValue\\n\\t\\t\\t/>\\n\\t\\t}\\n\\t\\t// ...\\n\\t);\\n```\\n\\nThe control for selecting a font should now be available under Paragraph options. Saving the choice now works, but the font stays the same both in the editor and on the frontend.\\n\\n![Font Picker](/img/blog/font-picker.webp)\\n\\nThe final step we need to make this work is to add a CSS rule that consumes our variable to **_/src/Blocks/components/paragraph/paragraph-style.scss_**:\\n```scss\\n.paragraph {\\n\\t// ...\\n\\tfont-family: var(--paragraph-font-family, var(--base-font-family));\\n\\t// ...\\n}\\n```\\n\\nAfter adding this single line of CSS code, your new option for selecting fonts will now be fully functional.\\n\\n## Closing thoughts\\nAdding fonts to a project is something you will usually only do when setting up a new project and then forget about it. As you could see in this blog post, this isn\'t a complicated process, but it has a specific set of steps that have to be taken in order for custom fonts to work in your project.\\n\\nOf course, there are other ways to include fonts in your project, but the described process is what we recommend and use. This is the (Eightshift) Way."},{"id":"modifying-blocks-color-theme","metadata":{"permalink":"/blog/modifying-blocks-color-theme","source":"@site/blog/2022-03-04-modifying-blocks.md","title":"Modifying blocks - Color Theme","description":"Step-by-step guide on how to modify and expand the functionality of existing blocks.","date":"2022-03-22T00:00:00.000Z","formattedDate":"March 22, 2022","tags":[{"label":"eightshift","permalink":"/blog/tags/eightshift"},{"label":"boilerplate","permalink":"/blog/tags/boilerplate"},{"label":"components","permalink":"/blog/tags/components"},{"label":"blocks","permalink":"/blog/tags/blocks"}],"readingTime":8.465,"hasTruncateMarker":true,"authors":[{"name":"Igor Obradovi\u0107","title":"WordPress Engineer","url":"https://github.com/iobrado","imageURL":"https://avatars.githubusercontent.com/u/23059501?v=4","key":"obradovic"}],"frontMatter":{"title":"Modifying blocks - Color Theme","description":"Step-by-step guide on how to modify and expand the functionality of existing blocks.","slug":"modifying-blocks-color-theme","authors":"obradovic","date":"2022-03-22T00:00:00.000Z","tags":["eightshift","boilerplate","components","blocks"],"hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"Adding fonts","permalink":"/blog/adding-fonts"},"nextItem":{"title":"Adding components and blocks with WP-CLI","permalink":"/blog/adding-blocks-wpcli"}},"content":"In our previous post, we covered how to use Storybook and WP-CLI to add additional blocks to your project. This post will walk you through modifying an existing block step-by-step.\\n\x3c!--truncate--\x3e\\n\\nSince Eightshift Development kit is a starter theme, made for developers to jumpstart and speed up their development, you\'re welcome to modify files in it directly. There is no need to create a child theme to protect changes from updates.\\n\\n## Modifying a block or a component?\\nBecause we used the Quote block in our previous post, we will continue using it as an example as it\'s a fairly simple one. One of the first questions you may ask yourself could be: \\"Should I modify a component or a block?\\". And the answer is - it depends.\\n\\nIf you compare **_components/quote/quote.php_** (component) and **_custom/quote/quote.php_** (block), you\'ll notice that most of the HTML code is inside the component, while the block pretty much only renders the Quote component inside a wrapper. This example will walk you through the whole process of adding a new attribute and its options to a block.\\n\\n## What will we do?\\n\\nWe want to style the block and add a new feature. An option to choose a color theme for the Quote block. These are the specs:\\n- each Quote block has a background with rounded corners\\n- option for three color themes: blue, green, yellow\\n- the background has a lighter shade of the selected color\\n- quote icon has a stronger shade of the selected color\\n\\nTo better help you visualize, this is how the Quote block should look like after making these changes, showcasing all three color theme variations:\\n![Color Theme Examples](/img/blog/color-theme-examples.webp)\\n\\n## Adding background\\n\\nInitial background styling is fairly straightforward. Navigate to **_src/Blocks/components/quote/quote-style.scss_** and paste the following code inside `.quote` class:\\n\\n```css\\npadding: calc(var(--base-font-size) * 1.6rem);\\nborder-radius: calc(var(--base-font-size) * 2rem);\\nbackground-color: global-settings(colors, light);\\n```\\nYou may notice we\'re using `calc` instead of directly writing values in rems. This way makes it much easier to calculate pixel size since `calc(var(--base-font-size) * 1.6rem)` equals `16px`.\\n\\nFor now, we will add a simple light grey background to see how it looks. We\'ll replace this value later with a CSS variable.\\n\\n:::tip\\nDon\'t hardcode hex color values directly inside your component. Instead, use colors defined in your global manifest.\\n:::\\n\\nYou\'ll notice that the changes are visible both in the editor and on the frontend. Since the Gutenberg editor adds some additional markup, sometimes you\'ll need to add additional styling only for the editor. In case we need to override something in the editor for our Quote component, we would simply create **_quote-editor.scss_**.\\n\\n## Adding new colors to your project\\n\\nBecause the theme currently doesn\'t have all the required colors, we need to add additional colors which will be used for the color theme feature. We will use the colors already defined in the manifest for icon color, but we need to add lighter variations of those colors to use them for the background. Navigate to your global manifest, which is located inside **_src/Blocks/manifest.json_** and add the following values inside `colors`:\\n```json\\n{\\n\\t\\"name\\": \\"Light Blue\\",\\n\\t\\"slug\\": \\"light-blue\\",\\n\\t\\"color\\": \\"#B3E5FC\\"\\n},\\n{\\n\\t\\"name\\": \\"Light Green\\",\\n\\t\\"slug\\": \\"light-green\\",\\n\\t\\"color\\": \\"#DCEDC8\\"\\n},\\n{\\n\\t\\"name\\": \\"Light Yellow\\",\\n\\t\\"slug\\": \\"light-yellow\\",\\n\\t\\"color\\": \\"#FFF9C4\\"\\n}\\n```\\n\\n## Adding a new attribute and options to manifest\\n\\nFor editors to be able to choose which color theme to use for the Quote block, we need to define an attribute for it in the manifest. Navigate to **_src/Blocks/components/quote/manifest.json_** and add the following value inside `attributes`:\\n\\n```json\\n\\"quoteColorTheme\\": {\\n\\t\\"type\\": \\"string\\",\\n\\t\\"default\\": \\"blue\\"\\n}\\n```\\n\\n:::caution\\nDouble-check the path of the manifest used in this example. We\'re adding it inside the Quote component manifest, not the Quote block manifest.\\n:::\\n\\nAfter that, since we want to have a fixed number of options, we need to define available options. We can do that inside `options` which is on the same level as `attributes`:\\n```json\\n\\"options\\": {\\n\\t\\"quoteColorTheme\\": [\\n\\t\\t\\"blue\\",\\n\\t\\t\\"green\\",\\n\\t\\t\\"yellow\\"\\n\\t]\\n}\\n```\\n\\n## CSS variables\\n\\nOur next step is to add CSS variables to the Quote component\'s manifest. Inside **_manifest.json_**, on the same level as `attributes`, add the following code:\\n```json\\n\\"variables\\": {\\n\\t\\"quoteColorTheme\\": {\\n\\t\\t\\"blue\\": [\\n\\t\\t\\t{\\n\\t\\t\\t\\t\\"variable\\": {\\n\\t\\t\\t\\t\\t\\"quote-background-color\\": \\"var(--global-colors-light-blue)\\",\\n\\t\\t\\t\\t\\t\\"quote-icon-color\\": \\"var(--global-colors-blue)\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t],\\n\\t\\t\\"green\\": [\\n\\t\\t\\t{\\n\\t\\t\\t\\t\\"variable\\": {\\n\\t\\t\\t\\t\\t\\"quote-background-color\\": \\"var(--global-colors-light-green)\\",\\n\\t\\t\\t\\t\\t\\"quote-icon-color\\": \\"var(--global-colors-green)\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t],\\n\\t\\t\\"yellow\\": [\\n\\t\\t\\t{\\n\\t\\t\\t\\t\\"variable\\": {\\n\\t\\t\\t\\t\\t\\"quote-background-color\\": \\"var(--global-colors-light-yellow)\\",\\n\\t\\t\\t\\t\\t\\"quote-icon-color\\": \\"var(--global-colors-yellow)\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t]\\n\\t}\\n}\\n```\\n\\nNow, navigate back to Quote component\'s **_quote-style.scss_** and replace the `background-color` which we used for testing with the following:\\n```css\\nbackground-color: var(--quote-background-color);\\n```\\n\\nAs you can see, the variable name is the same one we used when defining background color variations in the manifest. For icon color, we do the same. After adding a new color to `&__icon` selector, our code should now look like this:\\n\\n```css\\n&__icon {\\n\\tdisplay: block;\\n\\tmargin-bottom: 1rem;\\n\\tcolor: var(--quote-icon-color);\\n}\\n```\\n\\n## Outputting CSS variables in editor\\nTo make our color theme visible in editor, we have to add few lines of code to **_src/Blocks/components/quote/components/quote-editor.js_** file. First, we need to import a few functions. We need `useMemo` from **_react_**, `outputCssVariables` and `getUnique` from **_@eightshift/frontend-libs/scripts_** and finally, we need data from the global manifest.\\n\\nAfter importing these and defining a unique constant, your code should look like this:\\n\\n```js\\nimport React, { useMemo } from \'react\';\\nimport classnames from \'classnames\';\\nimport { checkAttr, props, selector, outputCssVariables, getUnique } from \'@eightshift/frontend-libs/scripts\';\\nimport { HeadingEditor } from \'../../heading/components/heading-editor\';\\nimport { ParagraphEditor } from \'../../paragraph/components/paragraph-editor\';\\nimport manifest from \'./../manifest.json\';\\nimport globalManifest from \'./../../../manifest.json\';\\n\\nexport const QuoteEditor = (attributes) => {\\n\\tconst unique = useMemo(() => getUnique(), []);\\n\\t//...\\n```\\nNext, we need to add a unique `data-id` and output the `