docusaurus/assets/js/3c03c7ff.e786ae3d.js
2025-03-14 17:25:10 +00:00

1 line
No EOL
56 KiB
JavaScript

"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([["51719"],{10487:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-create-project-2fe25e5974adc027f7ac2c36f1717de1.png"},91039:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-download-translations-warning-cb747bdcfc38beabea491f63781b9b40.png"},94344:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-files-rename-5ff9bf66f6123800006cf38ead6b66ae.png"},26317:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-french-translations-4f252e7f63a781628378eeb3a9fefb39.png"},4297:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-hide-string-5e470a33a42e044379bf6860ff534b50.png"},5804:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-settings-duplicate-strings-6943e2e4cab8910625a0b6c285052726.png"},37166:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-source-files-0ba8c0433421141418462c3bf6de22ed.png"},10377:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-translate-json-39a6b98aa60a84eba22b2f5834cbb97d.png"},86688:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-translate-markdown-0651cd22b5f07f0e9b3031ffaebbc5b6.png"},2882:function(e,n,s){s.d(n,{Z:()=>t});let t=s.p+"assets/images/crowdin-upload-sources-cli-5758c2f4d4256ac05d21b637d1d517bb.png"},81269:function(e,n,s){s.r(n),s.d(n,{frontMatter:()=>c,default:()=>g,contentTitle:()=>d,assets:()=>u,toc:()=>h,metadata:()=>t});var t=JSON.parse('{"id":"i18n/crowdin","title":"i18n - Using Crowdin","description":"The i18n system of Docusaurus is decoupled from any translation software.","source":"@site/docs/i18n/i18n-crowdin.mdx","sourceDirName":"i18n","slug":"/i18n/crowdin","permalink":"/docs/i18n/crowdin","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/edit/main/website/docs/i18n/i18n-crowdin.mdx","tags":[],"version":"current","lastUpdatedBy":"S\xe9bastien Lorber","lastUpdatedAt":1741972920000,"frontMatter":{"id":"crowdin","slug":"/i18n/crowdin","toc_max_heading_level":4},"sidebar":"docs","previous":{"title":"Using Git","permalink":"/docs/i18n/git"},"next":{"title":"What\'s next?","permalink":"/docs/guides/whats-next"}}'),r=s(85893),i=s(80980),a=s(15398),o=s(58636),l=s(95998);let c={id:"crowdin",slug:"/i18n/crowdin",toc_max_heading_level:4},d="i18n - Using Crowdin",u={},h=[{value:"Crowdin overview",id:"crowdin-overview",level:2},{value:"Crowdin tutorial",id:"crowdin-tutorial",level:2},{value:"Prepare the Docusaurus site",id:"prepare-the-docusaurus-site",level:3},{value:"Create a Crowdin project",id:"create-a-crowdin-project",level:3},{value:"Create the Crowdin configuration",id:"create-the-crowdin-configuration",level:3},{value:"Access token",id:"access-token",level:4},{value:"Other configuration fields",id:"other-configuration-fields",level:4},{value:"Install the Crowdin CLI",id:"install-the-crowdin-cli",level:3},{value:"Upload the sources",id:"upload-the-sources",level:3},{value:"Translate the sources",id:"translate-the-sources",level:3},{value:"Download the translations",id:"download-the-translations",level:3},{value:"Automate with CI",id:"automate-with-ci",level:3},{value:"Advanced Crowdin topics",id:"advanced-crowdin-topics",level:2},{value:"MDX",id:"mdx",level:3},{value:"MDX problems",id:"mdx-problems",level:4},{value:"MDX solutions",id:"mdx-solutions",level:4},{value:"Docs versioning",id:"docs-versioning",level:3},{value:"Multi-instance plugins",id:"multi-instance-plugins",level:3},{value:"Maintaining your site",id:"maintaining-your-site",level:3},{value:"VCS (Git) integrations",id:"vcs-git-integrations",level:3},{value:"In-Context localization",id:"in-context-localization",level:3},{value:"Localize edit URLs",id:"localize-edit-urls",level:3},{value:"Example configuration",id:"example-configuration",level:3},{value:"Machine Translation (MT) issue: links/image handling",id:"machine-translation-mt-issue-linksimage-handling",level:3}];function p(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"i18n---using-crowdin",children:"i18n - Using Crowdin"})}),"\n",(0,r.jsxs)(n.p,{children:["The i18n system of Docusaurus is ",(0,r.jsx)(n.strong,{children:"decoupled from any translation software"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You can integrate Docusaurus with the ",(0,r.jsx)(n.strong,{children:"tools and SaaS of your choice"}),", as long as you put the ",(0,r.jsx)(n.strong,{children:"translation files at the correct location"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["We document the usage of ",(0,r.jsx)(n.a,{href:"https://crowdin.com/",children:"Crowdin"}),", as ",(0,r.jsx)(n.strong,{children:"one"})," possible ",(0,r.jsx)(n.strong,{children:"integration example"}),"."]}),"\n",(0,r.jsxs)(n.admonition,{type:"warning",children:[(0,r.jsxs)(n.p,{children:["This is ",(0,r.jsx)(n.strong,{children:"not an endorsement of Crowdin"})," as the unique choice to translate a Docusaurus site, but it is successfully used by Facebook to translate documentation projects such as ",(0,r.jsx)(n.a,{href:"https://jestjs.io/",children:"Jest"}),", ",(0,r.jsx)(n.a,{href:"https://docusaurus.io/",children:"Docusaurus"}),", and ",(0,r.jsx)(n.a,{href:"https://reasonml.github.io/",children:"ReasonML"}),"."]}),(0,r.jsxs)(n.p,{children:["Refer to the ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://support.crowdin.com/",children:"Crowdin documentation"})})," and ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"mailto:support@crowdin.com",children:"Crowdin support"})})," for help."]})]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Use this ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://github.com/facebook/docusaurus/discussions/4052",children:"community-driven GitHub discussion"})})," to discuss anything related to Docusaurus + Crowdin."]})}),"\n",(0,r.jsx)(n.h2,{id:"crowdin-overview",children:"Crowdin overview"}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin is a translation SaaS, offering a ",(0,r.jsx)(n.a,{href:"https://crowdin.com/page/open-source-project-setup-request",children:"free plan for open-source projects"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"We recommend the following translation workflow:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Upload sources"})," to Crowdin (untranslated files)"]}),"\n",(0,r.jsxs)(n.li,{children:["Use Crowdin to ",(0,r.jsx)(n.strong,{children:"translate the content"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Download translations"})," from Crowdin (localized translation files)"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin provides a ",(0,r.jsx)(n.a,{href:"https://support.crowdin.com/cli-tool/",children:"CLI"})," to ",(0,r.jsx)(n.strong,{children:"upload sources"})," and ",(0,r.jsx)(n.strong,{children:"download translations"}),", allowing you to automate the translation process."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsxs)(n.a,{href:"https://support.crowdin.com/configuration-file/",children:[(0,r.jsx)(n.code,{children:"crowdin.yml"})," configuration file"]})," is convenient for Docusaurus, and permits to ",(0,r.jsx)(n.strong,{children:"download the localized translation files at the expected location"})," (in ",(0,r.jsx)(n.code,{children:"i18n/[locale]/.."}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["Read the ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://support.crowdin.com/",children:"official documentation"})})," to know more about advanced features and different translation workflows."]}),"\n",(0,r.jsx)(n.h2,{id:"crowdin-tutorial",children:"Crowdin tutorial"}),"\n",(0,r.jsxs)(n.p,{children:["This is a walk-through of using Crowdin to translate a newly initialized English Docusaurus website into French, and assume you already followed the ",(0,r.jsx)(n.a,{href:"/docs/i18n/tutorial",children:"i18n tutorial"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The end result can be seen at ",(0,r.jsx)(n.a,{href:"https://docusaurus-crowdin-example.netlify.app/",children:"docusaurus-crowdin-example.netlify.app"})," (",(0,r.jsx)(n.a,{href:"https://github.com/slorber/docusaurus-crowdin-example",children:"repository"}),")."]}),"\n",(0,r.jsx)(n.h3,{id:"prepare-the-docusaurus-site",children:"Prepare the Docusaurus site"}),"\n",(0,r.jsx)(n.p,{children:"Initialize a new Docusaurus site:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npx create-docusaurus@latest website classic\n"})}),"\n",(0,r.jsx)(n.p,{children:"Add the site configuration for the French language:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="docusaurus.config.js"',children:"export default {\n i18n: {\n defaultLocale: 'en',\n locales: ['en', 'fr'],\n },\n themeConfig: {\n navbar: {\n items: [\n // ...\n {\n type: 'localeDropdown',\n position: 'left',\n },\n // ...\n ],\n },\n },\n // ...\n};\n"})}),"\n",(0,r.jsx)(n.p,{children:"Translate the homepage:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-jsx",metastring:'title="src/pages/index.js"',children:"import React from 'react';\nimport Translate from '@docusaurus/Translate';\nimport Layout from '@theme/Layout';\n\nexport default function Home() {\n return (\n <Layout>\n <h1 style={{margin: 20}}>\n <Translate description=\"The homepage main heading\">\n Welcome to my Docusaurus translated site!\n </Translate>\n </h1>\n </Layout>\n );\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"create-a-crowdin-project",children:"Create a Crowdin project"}),"\n",(0,r.jsxs)(n.p,{children:["Sign up on ",(0,r.jsx)(n.a,{href:"https://crowdin.com/",children:"Crowdin"}),", and create a project."]}),"\n",(0,r.jsx)(n.p,{children:"Use English as the source language, and French as the target language."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Create a Crowdin project with english as source language, and french as target language",src:s(10487).Z+"",width:"1868",height:"1576"})}),"\n",(0,r.jsx)(n.p,{children:"Your project is created, but it is empty for now. We will upload the files to translate in the next steps."}),"\n",(0,r.jsx)(n.h3,{id:"create-the-crowdin-configuration",children:"Create the Crowdin configuration"}),"\n",(0,r.jsxs)(n.p,{children:["This configuration (",(0,r.jsx)(n.a,{href:"https://support.crowdin.com/configuration-file/",children:"doc"}),") provides a mapping for the Crowdin CLI to understand:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Where to find the source files to upload (JSON and Markdown)"}),"\n",(0,r.jsxs)(n.li,{children:["Where to download the files after translation (in ",(0,r.jsx)(n.code,{children:"i18n/[locale]"}),")"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Create ",(0,r.jsx)(n.code,{children:"crowdin.yml"})," in ",(0,r.jsx)(n.code,{children:"website"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yml",metastring:'title="crowdin.yml"',children:"project_id: '123456'\napi_token_env: CROWDIN_PERSONAL_TOKEN\npreserve_hierarchy: true\nfiles:\n # JSON translation files\n - source: /i18n/en/**/*\n translation: /i18n/%two_letters_code%/**/%original_file_name%\n # Docs Markdown files\n - source: /docs/**/*\n translation: /i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%\n # Blog Markdown files\n - source: /blog/**/*\n translation: /i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%\n"})}),"\n",(0,r.jsx)(n.p,{children:"Crowdin has its own syntax for declaring source/translation paths:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"**/*"}),": everything in a subfolder"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"%two_letters_code%"}),": the 2-letters variant of Crowdin target languages (",(0,r.jsx)(n.code,{children:"fr"})," in our case)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"**/%original_file_name%"}),": the translations will preserve the original folder/file hierarchy"]}),"\n"]}),"\n",(0,r.jsxs)(n.admonition,{type:"info",children:[(0,r.jsx)(n.p,{children:"The Crowdin CLI warnings are not always easy to understand."}),(0,r.jsx)(n.p,{children:"We advise to:"}),(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"change one thing at a time"}),"\n",(0,r.jsx)(n.li,{children:"re-upload sources after any configuration change"}),"\n",(0,r.jsxs)(n.li,{children:["use paths starting with ",(0,r.jsx)(n.code,{children:"/"})," (",(0,r.jsx)(n.code,{children:"./"})," does not work)"]}),"\n",(0,r.jsxs)(n.li,{children:["avoid fancy globbing patterns like ",(0,r.jsx)(n.code,{children:"/docs/**/*.(md|mdx)"})," (does not work)"]}),"\n"]})]}),"\n",(0,r.jsx)(n.h4,{id:"access-token",children:"Access token"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"api_token_env"})," attribute defines the ",(0,r.jsx)(n.strong,{children:"env variable name"})," read by the Crowdin CLI."]}),"\n",(0,r.jsxs)(n.p,{children:["You can obtain a ",(0,r.jsx)(n.code,{children:"Personal Access Token"})," on ",(0,r.jsx)(n.a,{href:"https://crowdin.com/settings#api-key",children:"your personal profile page"}),"."]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["You can keep the default value ",(0,r.jsx)(n.code,{children:"CROWDIN_PERSONAL_TOKEN"}),", and set this environment variable and on your computer and on the CI server to the generated access token."]})}),"\n",(0,r.jsxs)(n.admonition,{type:"warning",children:[(0,r.jsxs)(n.p,{children:["A Personal Access Tokens grant ",(0,r.jsx)(n.strong,{children:"read-write access to all your Crowdin projects"}),"."]}),(0,r.jsxs)(n.p,{children:["You should ",(0,r.jsx)(n.strong,{children:"not commit"})," it, and it may be a good idea to create a dedicated ",(0,r.jsx)(n.strong,{children:"Crowdin profile for your company"})," instead of using a personal account."]})]}),"\n",(0,r.jsx)(n.h4,{id:"other-configuration-fields",children:"Other configuration fields"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"project_id"}),": can be hardcoded, and is found on ",(0,r.jsx)(n.code,{children:"https://crowdin.com/project/<MY_PROJECT_NAME>/settings#api"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"preserve_hierarchy"}),": preserve the folder's hierarchy of your docs on Crowdin UI instead of flattening everything"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"install-the-crowdin-cli",children:"Install the Crowdin CLI"}),"\n",(0,r.jsxs)(n.p,{children:["This tutorial uses the CLI version ",(0,r.jsx)(n.code,{children:"3.5.2"}),", but we expect ",(0,r.jsx)(n.code,{children:"3.x"})," releases to keep working."]}),"\n",(0,r.jsx)(n.p,{children:"Install the Crowdin CLI as an npm package to your Docusaurus site:"}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm install @crowdin/cli@3\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn add @crowdin/cli@3\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm add @crowdin/cli@3\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun add @crowdin/cli@3\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["Add a ",(0,r.jsx)(n.code,{children:"crowdin"})," script:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",metastring:'title="package.json"',children:'{\n "scripts": {\n // ...\n "write-translations": "docusaurus write-translations",\n "crowdin": "crowdin"\n }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Test that you can run the Crowdin CLI:"}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm run crowdin -- --version\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn crowdin --version\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm run crowdin --version\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun run crowdin --version\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["Set the ",(0,r.jsx)(n.code,{children:"CROWDIN_PERSONAL_TOKEN"})," env variable on your computer, to allow the CLI to authenticate with the Crowdin API."]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Temporarily, you can hardcode your personal token in ",(0,r.jsx)(n.code,{children:"crowdin.yml"})," with ",(0,r.jsx)(n.code,{children:"api_token: 'MY-TOKEN'"}),"."]})}),"\n",(0,r.jsx)(n.h3,{id:"upload-the-sources",children:"Upload the sources"}),"\n",(0,r.jsxs)(n.p,{children:["Generate the JSON translation files for the default language in ",(0,r.jsx)(n.code,{children:"website/i18n/en"}),":"]}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm run write-translations\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn write-translations\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm run write-translations\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun run write-translations\n"})})})]}),"\n",(0,r.jsx)(n.p,{children:"Upload all the JSON and Markdown translation files:"}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm run crowdin upload\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn crowdin upload\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm run crowdin upload\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun run crowdin upload\n"})})})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin CLI uploading Docusaurus source files",src:s(2882).Z+"",width:"1156",height:"682"})}),"\n",(0,r.jsxs)(n.p,{children:["Your source files are now visible on the Crowdin interface: ",(0,r.jsx)(n.code,{children:"https://crowdin.com/project/<MY_PROJECT_NAME>/settings#files"})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI showing Docusaurus source files",src:s(37166).Z+"",width:"2462",height:"1048"})}),"\n",(0,r.jsx)(n.h3,{id:"translate-the-sources",children:"Translate the sources"}),"\n",(0,r.jsxs)(n.p,{children:["On ",(0,r.jsx)(n.code,{children:"https://crowdin.com/project/<MY_PROJECT_NAME>"}),", click on the French target language."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI showing French translation files",src:s(26317).Z+"",width:"2430",height:"1736"})}),"\n",(0,r.jsx)(n.p,{children:"Translate some Markdown files."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI to translate a Markdown file",src:s(86688).Z+"",width:"3044",height:"1586"})}),"\n",(0,r.jsxs)(n.admonition,{type:"tip",children:[(0,r.jsxs)(n.p,{children:["Use ",(0,r.jsx)(n.code,{children:"Hide String"})," to make sure translators ",(0,r.jsx)(n.strong,{children:"don't translate things that should not be"}),":"]}),(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Front matter: ",(0,r.jsx)(n.code,{children:"id"}),", ",(0,r.jsx)(n.code,{children:"slug"}),", ",(0,r.jsx)(n.code,{children:"tags"})," ..."]}),"\n",(0,r.jsxs)(n.li,{children:["Admonitions: ",(0,r.jsx)(n.code,{children:":::"}),", ",(0,r.jsx)(n.code,{children:":::note"}),", ",(0,r.jsx)(n.code,{children:":::tip"})," ..."]}),"\n"]}),(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI hide string",src:s(4297).Z+"",width:"1518",height:"698"})})]}),"\n",(0,r.jsx)(n.p,{children:"Translate some JSON files."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI to translate a JSON file",src:s(10377).Z+"",width:"2868",height:"1490"})}),"\n",(0,r.jsx)(n.admonition,{type:"info",children:(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"description"})," attribute of JSON translation files is visible on Crowdin to help translate the strings."]})}),"\n",(0,r.jsxs)(n.admonition,{type:"tip",children:[(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://support.crowdin.com/pre-translation-via-machine/",children:"Pre-translate"})})," your site, and ",(0,r.jsx)(n.strong,{children:"fix pre-translation mistakes manually"})," (enable the Global Translation Memory in settings first)."]}),(0,r.jsxs)(n.p,{children:["Use the ",(0,r.jsx)(n.code,{children:"Hide String"})," feature first, as Crowdin is pre-translating things too optimistically."]})]}),"\n",(0,r.jsx)(n.h3,{id:"download-the-translations",children:"Download the translations"}),"\n",(0,r.jsx)(n.p,{children:"Use the Crowdin CLI to download the translated JSON and Markdown files."}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm run crowdin download\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn crowdin download\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm run crowdin download\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun run crowdin download\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["The translated content should be downloaded in ",(0,r.jsx)(n.code,{children:"i18n/fr"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Start your site on the French locale:"}),"\n",(0,r.jsxs)(a.Z,{groupId:"npm2yarn",children:[(0,r.jsx)(o.Z,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm run start -- --locale fr\n"})})}),(0,r.jsx)(o.Z,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn run start --locale fr\n"})})}),(0,r.jsx)(o.Z,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm run start --locale fr\n"})})}),(0,r.jsx)(o.Z,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun run start --locale fr\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["Make sure that your website is now translated in French at ",(0,r.jsx)(n.a,{href:"http://localhost:3000/fr/",children:(0,r.jsx)(n.code,{children:"http://localhost:3000/fr/"})}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"automate-with-ci",children:"Automate with CI"}),"\n",(0,r.jsxs)(n.p,{children:["We will configure the CI to ",(0,r.jsx)(n.strong,{children:"download the Crowdin translations at build time"})," and keep them outside of Git."]}),"\n",(0,r.jsxs)(n.p,{children:["Add ",(0,r.jsx)(n.code,{children:"website/i18n"})," to ",(0,r.jsx)(n.code,{children:".gitignore"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Set the ",(0,r.jsx)(n.code,{children:"CROWDIN_PERSONAL_TOKEN"})," env variable on your CI."]}),"\n",(0,r.jsxs)(n.p,{children:["Create an npm script to ",(0,r.jsx)(n.code,{children:"sync"})," Crowdin (extract sources, upload sources, download translations):"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",metastring:'title="package.json"',children:'{\n "scripts": {\n "crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download"\n }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Call the ",(0,r.jsx)(n.code,{children:"npm run crowdin:sync"})," script in your CI, just before building the Docusaurus site."]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Keep your deploy-previews fast: don't download translations, and use ",(0,r.jsx)(n.code,{children:"npm run build -- --locale en"})," for feature branches."]})}),"\n",(0,r.jsx)(n.admonition,{type:"warning",children:(0,r.jsx)(n.p,{children:"Crowdin does not support well multiple concurrent uploads/downloads: it is preferable to only include translations to your production deployment, and keep deploy previews untranslated."})}),"\n",(0,r.jsx)(n.h2,{id:"advanced-crowdin-topics",children:"Advanced Crowdin topics"}),"\n",(0,r.jsx)(n.h3,{id:"mdx",children:"MDX"}),"\n",(0,r.jsx)(n.admonition,{type:"warning",children:(0,r.jsx)(n.p,{children:"Pay special attention to the JSX fragments in MDX documents!"})}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin ",(0,r.jsx)(n.strong,{children:"does not support officially MDX"}),", but they added ",(0,r.jsxs)(n.strong,{children:["support for the ",(0,r.jsx)(n.code,{children:".mdx"})," extension"]}),", and interpret such files as Markdown (instead of plain text)."]}),"\n",(0,r.jsx)(n.h4,{id:"mdx-problems",children:"MDX problems"}),"\n",(0,r.jsx)(n.p,{children:"Crowdin thinks that the JSX syntax is embedded HTML and can mess up with the JSX markup when you download the translations, leading to a site that fails to build due to invalid JSX."}),"\n",(0,r.jsxs)(n.p,{children:["Simple JSX fragments using simple string props like ",(0,r.jsx)(n.code,{children:'<Username name="Sebastien"/>'})," will work fine; more complex JSX fragments using object/array props like ",(0,r.jsx)(n.code,{children:'<User person={{name: "Sebastien"}}/>'})," are more likely to fail due to a syntax that does not look like HTML."]}),"\n",(0,r.jsx)(n.h4,{id:"mdx-solutions",children:"MDX solutions"}),"\n",(0,r.jsxs)(n.p,{children:["We recommend extracting the complex embedded JSX code as separate standalone components. We also added an ",(0,r.jsx)(n.code,{children:"mdx-code-block"})," escape hatch syntax:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'# How to deploy Docusaurus\n\nTo deploy Docusaurus, run the following command:\n\n````mdx-code-block\xa0\n\nimport Tabs from \'@theme/Tabs\';\n\nimport TabItem from \'@theme/TabItem\';\n\n<Tabs>\n <TabItem value="bash" label="Bash">\n\n ```bash\n GIT_USER=<GITHUB_USERNAME> yarn deploy\n ```\n\n </TabItem>\n <TabItem value="windows" label="Windows">\n\n ```batch\n cmd /C "set "GIT_USER=<GITHUB_USERNAME>" && yarn deploy"\n ```\n\n </TabItem>\n</Tabs>\n````\n'})}),"\n",(0,r.jsx)(n.p,{children:"This will:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"be interpreted by Crowdin as code blocks (and not mess-up with the markup on download)"}),"\n",(0,r.jsx)(n.li,{children:"be interpreted by Docusaurus as regular JSX (as if it was not wrapped by any code block)"}),"\n",(0,r.jsx)(n.li,{children:"unfortunately opt-out of MDX tooling (IDE syntax highlighting, Prettier...)"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"docs-versioning",children:"Docs versioning"}),"\n",(0,r.jsxs)(n.p,{children:["Configure translation files for the ",(0,r.jsx)(n.code,{children:"website/versioned_docs"})," folder."]}),"\n",(0,r.jsxs)(n.p,{children:["When creating a new version, the source strings will generally be quite similar to the current version (",(0,r.jsx)(n.code,{children:"website/docs"}),"), and you don't want to translate the new version docs again and again."]}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin provides a ",(0,r.jsx)(n.code,{children:"Duplicate Strings"})," setting."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin Duplicate Strings option setting",src:s(5804).Z+"",width:"1422",height:"368"})}),"\n",(0,r.jsxs)(n.p,{children:["We recommend using ",(0,r.jsx)(n.code,{children:"Hide"}),", but the ideal setting depends on how much your versions are different."]}),"\n",(0,r.jsx)(n.admonition,{type:"warning",children:(0,r.jsxs)(n.p,{children:["Not using ",(0,r.jsx)(n.code,{children:"Hide"})," leads to a much larger amount of ",(0,r.jsx)(n.code,{children:"source strings"})," in quotas, and will affect the pricing."]})}),"\n",(0,r.jsx)(n.h3,{id:"multi-instance-plugins",children:"Multi-instance plugins"}),"\n",(0,r.jsx)(n.p,{children:"You need to configure translation files for each plugin instance."}),"\n",(0,r.jsxs)(n.p,{children:["If you have a docs plugin instance with ",(0,r.jsx)(n.code,{children:"id=ios"}),", you will need to configure those source files as well"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"website/ios"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"website/ios_versioned_docs"})," (if versioned)"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"maintaining-your-site",children:"Maintaining your site"}),"\n",(0,r.jsxs)(n.p,{children:["Sometimes, you will ",(0,r.jsx)(n.strong,{children:"remove or rename a source file"})," on Git, and Crowdin will display CLI warnings:"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin CLI: download translation warning",src:s(91039).Z+"",width:"2268",height:"886"})}),"\n",(0,r.jsxs)(n.p,{children:["When your sources are refactored, you should use the Crowdin UI to ",(0,r.jsx)(n.strong,{children:"update your Crowdin files manually"}),":"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Crowdin UI: renaming a file",src:s(94344).Z+"",width:"2472",height:"1186"})}),"\n",(0,r.jsx)(n.h3,{id:"vcs-git-integrations",children:"VCS (Git) integrations"}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin has multiple VCS integrations for ",(0,r.jsx)(n.a,{href:"https://support.crowdin.com/github-integration/",children:"GitHub"}),", GitLab, Bitbucket."]}),"\n",(0,r.jsx)(n.admonition,{title:"TL;DR",type:"warning",children:(0,r.jsx)(n.p,{children:"We recommend avoiding them."})}),"\n",(0,r.jsxs)(n.p,{children:["It could have been helpful to be able to edit the translations in both Git and Crowdin, and have a ",(0,r.jsx)(n.strong,{children:"bi-directional sync"})," between the 2 systems."]}),"\n",(0,r.jsxs)(n.p,{children:["In practice, ",(0,r.jsx)(n.strong,{children:"it didn't work very reliably"})," for a few reasons:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"The Crowdin -> Git sync works fine (with a pull request)"}),"\n",(0,r.jsx)(n.li,{children:"The Git -> Crowdin sync is manual (you have to press a button)"}),"\n",(0,r.jsx)(n.li,{children:"The heuristics used by Crowdin to match existing Markdown translations to existing Markdown sources are not 100% reliable, and you have to verify the result on Crowdin UI after any sync from Git"}),"\n",(0,r.jsx)(n.li,{children:"2 users concurrently editing on Git and Crowdin can lead to a translation loss"}),"\n",(0,r.jsxs)(n.li,{children:["It requires the ",(0,r.jsx)(n.code,{children:"crowdin.yml"})," file to be at the root of the repository"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"in-context-localization",children:"In-Context localization"}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin has an ",(0,r.jsx)(n.a,{href:"https://support.crowdin.com/in-context-localization/",children:"In-Context localization"})," feature."]}),"\n",(0,r.jsxs)(n.admonition,{type:"warning",children:[(0,r.jsx)(n.p,{children:"Unfortunately, it does not work yet for technical reasons, but we have good hope it can be solved."}),(0,r.jsxs)(n.p,{children:["Crowdin replaces Markdown strings with technical IDs such as ",(0,r.jsx)(n.code,{children:"crowdin:id12345"}),", but it does so too aggressively, including hidden strings, and messes up with front matter, admonitions, JSX..."]})]}),"\n",(0,r.jsx)(n.h3,{id:"localize-edit-urls",children:"Localize edit URLs"}),"\n",(0,r.jsxs)(n.p,{children:["When the user is browsing a page at ",(0,r.jsx)(n.code,{children:"/fr/doc1"}),", the edit button will link by default to the unlocalized doc at ",(0,r.jsx)(n.code,{children:"website/docs/doc1.md"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You may prefer the edit button to link to the Crowdin interface instead by using the ",(0,r.jsx)(n.code,{children:"editUrl"})," function to customize the edit URLs on a per-locale basis."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="docusaurus.config.js"',children:"const DefaultLocale = 'en';\n\nexport default {\n presets: [\n [\n '@docusaurus/preset-classic',\n {\n docs: {\n // highlight-start\n editUrl: ({locale, versionDocsDirPath, docPath}) => {\n // Link to Crowdin for French docs\n if (locale !== DefaultLocale) {\n return `https://crowdin.com/project/docusaurus-v2/${locale}`;\n }\n // Link to GitHub for English docs\n return `https://github.com/facebook/docusaurus/edit/main/website/${versionDocsDirPath}/${docPath}`;\n },\n // highlight-end\n },\n blog: {\n // highlight-start\n editUrl: ({locale, blogDirPath, blogPath}) => {\n if (locale !== DefaultLocale) {\n return `https://crowdin.com/project/docusaurus-v2/${locale}`;\n }\n return `https://github.com/facebook/docusaurus/edit/main/website/${blogDirPath}/${blogPath}`;\n },\n // highlight-start\n },\n },\n ],\n ],\n};\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:["It is currently ",(0,r.jsx)(n.strong,{children:"not possible to link to a specific file"})," in Crowdin."]})}),"\n",(0,r.jsx)(n.h3,{id:"example-configuration",children:"Example configuration"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"Docusaurus configuration file"})," is a good example of using versioning and multi-instance:"]}),"\n","\n",(0,r.jsx)(l.Z,{className:"language-yaml",title:"crowdin.yml",children:'#\n# Your Crowdin credentials\n#\nproject_id: \'428890\'\napi_token_env: CROWDIN_PERSONAL_TOKEN\n# base_path: \'.\'\n# base_url: https://api.crowdin.com\n\n#\n# Choose file structure in Crowdin\n# e.g. true or false\n#\npreserve_hierarchy: true\n\n# We generally want to use the "two-letters-code" of a locale (ie the language)\n# But not for all locales!\n# "pt-BR" may be better to remain as "pt-BR" instead of being transformed to "pt"\n# Note: &/* is Yaml anchor syntax: https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/\nlanguages_mapping: &languages_mapping\n two_letters_code:\n pt-BR: pt-BR\n\n#\n# Files configuration\n#\nfiles:\n - source: /website/i18n/en/**/*\n translation: /website/i18n/%two_letters_code%/**/%original_file_name%\n languages_mapping: *languages_mapping\n - source: /website/docs/**/*\n translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%\n languages_mapping: *languages_mapping\n - source: /website/community/**/*\n translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs-community/current/**/%original_file_name%\n languages_mapping: *languages_mapping\n - source: /website/versioned_docs/**/*\n translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/**/%original_file_name%\n languages_mapping: *languages_mapping\n - source: /website/blog/**/*\n translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%\n languages_mapping: *languages_mapping\n - source: /website/src/pages/**/*\n translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-pages/**/%original_file_name%\n ignore: [/**/*.js, /**/*.jsx, /**/*.ts, /**/*.tsx, /**/*.css]\n languages_mapping: *languages_mapping\n#\n# Source files filter\n# e.g. "/resources/en/*.json"\n#\n#"source" : "/website/docs/**/*.md",\n#\n# Where translations will be placed\n# e.g. "/resources/docs/%two_letters_code%/%original_file_name%"\n#\n#"translation" : "/website/i18n/%language%/docs/current/%original_file_name%",\n#\n# Files or directories for ignore\n# e.g. ["/**/?.txt", "/**/[0-9].txt", "/**/*\\?*.txt"]\n#\n#"ignore" : [],\n#\n# The dest allows you to specify a file name in Crowdin\n# e.g. "/messages.json"\n#\n#"dest" : "",\n#\n# File type\n# e.g. "json"\n#\n#"type" : "",\n#\n# The parameter "update_option" is optional. If it is not set, after the files update the translations for changed strings will be removed. Use to fix typos and for minor changes in the source strings\n# e.g. "update_as_unapproved" or "update_without_changes"\n#\n#"update_option" : "",\n#\n# Start block (for XML only)\n#\n#\n# Defines whether to translate tags attributes.\n# e.g. 0 or 1 (Default is 1)\n#\n# "translate_attributes" : 1,\n#\n# Defines whether to translate texts placed inside the tags.\n# e.g. 0 or 1 (Default is 1)\n#\n# "translate_content" : 1,\n#\n# This is an array of strings, where each item is the XPaths to DOM element that should be imported\n# e.g. ["/content/text", "/content/text[@value]"]\n#\n# "translatable_elements" : [],\n#\n# Defines whether to split long texts into smaller text segments\n# e.g. 0 or 1 (Default is 1)\n#\n# "content_segmentation" : 1,\n#\n# End block (for XML only)\n#\n#\n# Start .properties block\n#\n#\n# Defines whether single quote should be escaped by another single quote or backslash in exported translations\n# e.g. 0 or 1 or 2 or 3 (Default is 3)\n# 0 - do not escape single quote;\n# 1 - escape single quote by another single quote;\n# 2 - escape single quote by backslash;\n# 3 - escape single quote by another single quote only in strings containing variables ( {0} ).\n#\n# "escape_quotes" : 3,\n#\n# Defines whether any special characters (=, :, ! and #) should be escaped by backslash in exported translations.\n# e.g. 0 or 1 (Default is 0)\n# 0 - do not escape special characters\n# 1 - escape special characters by a backslash\n#\n# "escape_special_characters": 0\n#\n#\n# End .properties block\n#\n#\n# Often software projects have custom names for the directories where translations are placed. crowdin-cli allows you to map your own languages to be understandable by Crowdin.\n#\n#"languages_mapping" : {\n# "two_letters_code" : {\n# "crowdin_language_code" : "local_name"\n# }\n#},\n#\n# Does the first line contain header?\n# e.g. true or false\n#\n#"first_line_contains_header" : true,\n#\n# for spreadsheets\n# e.g. "identifier,source_phrase,context,uk,ru,fr"\n#\n# "scheme" : "",\n'.split("\n").map(e=>!e.startsWith("#")&&e).filter(Boolean).join("\n")}),"\n",(0,r.jsx)(n.h3,{id:"machine-translation-mt-issue-linksimage-handling",children:"Machine Translation (MT) issue: links/image handling"}),"\n",(0,r.jsxs)(n.p,{children:["Crowdin recently rolled out some major changes to the markdown file format and now the links are treated differently than they were before. Before they were considered as tags, but now they appear as plain text. Because of these changes the plain text links are passed to the MT engine which attempts to translate the target, thus breaking the translation (for instance: this string ",(0,r.jsx)(n.code,{children:"Allez voir [ma merveilleuse page](/ma-merveilleuse-page)"})," is translated ",(0,r.jsx)(n.code,{children:"Check out [my wonderful page](/my-wonderful-page)"}),", and this breaks docusaurus i18n workflow as the page name should not be translated)."]}),"\n",(0,r.jsx)(n.p,{children:"As of 2023 Dec.7, they are not planning to change the logic of how links are treated, so you should have this in mind if you plan to use Crowdin with MT."})]})}function g(e={}){let{wrapper:n}={...(0,i.a)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(p,{...e})}):p(e)}},58636:function(e,n,s){s.d(n,{Z:()=>i});var t=s(85893);s(67294);var r=s(90496);function i(e){let{children:n,hidden:s,className:i}=e;return(0,t.jsx)("div",{role:"tabpanel",className:(0,r.Z)("tabItem_pnkT",i),hidden:s,children:n})}},15398:function(e,n,s){s.d(n,{Z:()=>f});var t=s(85893),r=s(67294),i=s(90496),a=s(54947),o=s(3620),l=s(844),c=s(97486),d=s(32263),u=s(16971);function h(e){return r.Children.toArray(e).filter(e=>"\n"!==e).map(e=>{if(!e||r.isValidElement(e)&&function(e){let{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw Error(`Docusaurus error: Bad <Tabs> child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`)})?.filter(Boolean)??[]}function p(e){let{value:n,tabValues:s}=e;return s.some(e=>e.value===n)}var g=s(71607);function m(e){let{className:n,block:s,selectedValue:r,selectValue:o,tabValues:l}=e,c=[],{blockElementScrollPositionUntilNextRender:d}=(0,a.o5)(),u=e=>{let n=e.currentTarget,s=l[c.indexOf(n)].value;s!==r&&(d(n),o(s))},h=e=>{let n=null;switch(e.key){case"Enter":u(e);break;case"ArrowRight":{let s=c.indexOf(e.currentTarget)+1;n=c[s]??c[0];break}case"ArrowLeft":{let s=c.indexOf(e.currentTarget)-1;n=c[s]??c[c.length-1]}}n?.focus()};return(0,t.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.Z)("tabs",{"tabs--block":s},n),children:l.map(e=>{let{value:n,label:s,attributes:a}=e;return(0,t.jsx)("li",{role:"tab",tabIndex:r===n?0:-1,"aria-selected":r===n,ref:e=>{c.push(e)},onKeyDown:h,onClick:u,...a,className:(0,i.Z)("tabs__item","tabItem_AQgk",a?.className,{"tabs__item--active":r===n}),children:s??n},n)})})}function x(e){let{lazy:n,children:s,selectedValue:a}=e,o=(Array.isArray(s)?s:[s]).filter(Boolean);if(n){let e=o.find(e=>e.props.value===a);return e?(0,r.cloneElement)(e,{className:(0,i.Z)("margin-top--md",e.props.className)}):null}return(0,t.jsx)("div",{className:"margin-top--md",children:o.map((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))})}function j(e){let n=function(e){let{defaultValue:n,queryString:s=!1,groupId:t}=e,i=function(e){let{values:n,children:s}=e;return(0,r.useMemo)(()=>{let e=n??h(s).map(e=>{let{props:{value:n,label:s,attributes:t,default:r}}=e;return{value:n,label:s,attributes:t,default:r}});return!function(e){let n=(0,d.lx)(e,(e,n)=>e.value===n.value);if(n.length>0)throw Error(`Docusaurus error: Duplicate values "${n.map(e=>e.value).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e},[n,s])}(e),[a,g]=(0,r.useState)(()=>(function(e){let{defaultValue:n,tabValues:s}=e;if(0===s.length)throw Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(n){if(!p({value:n,tabValues:s}))throw Error(`Docusaurus error: The <Tabs> has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${s.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}let t=s.find(e=>e.default)??s[0];if(!t)throw Error("Unexpected error: 0 tabValues");return t.value})({defaultValue:n,tabValues:i})),[m,x]=function(e){let{queryString:n=!1,groupId:s}=e,t=(0,o.k6)(),i=function(e){let{queryString:n=!1,groupId:s}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!s)throw Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return s??null}({queryString:n,groupId:s});return[(0,c._X)(i),(0,r.useCallback)(e=>{if(!i)return;let n=new URLSearchParams(t.location.search);n.set(i,e),t.replace({...t.location,search:n.toString()})},[i,t])]}({queryString:s,groupId:t}),[j,f]=function(e){let{groupId:n}=e,s=n?`docusaurus.tab.${n}`:null,[t,i]=(0,u.Nk)(s);return[t,(0,r.useCallback)(e=>{s&&i.set(e)},[s,i])]}({groupId:t}),w=(()=>{let e=m??j;return p({value:e,tabValues:i})?e:null})();return(0,l.Z)(()=>{w&&g(w)},[w]),{selectedValue:a,selectValue:(0,r.useCallback)(e=>{if(!p({value:e,tabValues:i}))throw Error(`Can't select invalid tab value=${e}`);g(e),x(e),f(e)},[x,f,i]),tabValues:i}}(e);return(0,t.jsxs)("div",{className:(0,i.Z)("tabs-container","tabList_Qoir"),children:[(0,t.jsx)(m,{...n,...e}),(0,t.jsx)(x,{...n,...e})]})}function f(e){let n=(0,g.Z)();return(0,t.jsx)(j,{...e,children:h(e.children)},String(n))}},56497:function(e,n,s){s.d(n,{Z:()=>i});var t=s(85893);s(67294);var r=s(71607);function i(e){let{children:n,fallback:s}=e;return(0,r.Z)()?(0,t.jsx)(t.Fragment,{children:n?.()}):s??null}},95998:function(e,n,s){s.d(n,{Z:()=>en});var t,r={};s.r(r),s.d(r,{ButtonExample:()=>N});var i=s(85893),a=s(67294),o=s(90496),l=s(71607),c=s(10075),d=s(77827),u=s(8156),h=s(56497),p=s(85108),g=s(45245),m=s(26378);function x(){let{prism:e}=(0,m.L)(),{colorMode:n}=(0,g.I)(),s=e.theme,t=e.darkTheme||s;return"dark"===n?t:s}var j=s(67490);function f(e){let{children:n}=e;return(0,i.jsx)("div",{className:(0,o.Z)("playgroundHeader_Tvsk"),children:n})}function w(){return(0,i.jsx)("div",{children:"Loading..."})}function b(){return(0,i.jsx)(h.Z,{fallback:(0,i.jsx)(w,{}),children:()=>(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(j.Z,{fallback:e=>(0,i.jsx)(p.Ac,{...e}),children:(0,i.jsx)(c.i5,{})}),(0,i.jsx)(c.IF,{})]})})}function y(){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(f,{children:(0,i.jsx)(d.Z,{id:"theme.Playground.result",description:"The result label of the live codeblocks",children:"Result"})}),(0,i.jsx)("div",{className:"playgroundPreview_mApW",children:(0,i.jsx)(b,{})})]})}function v(){let e=(0,l.Z)();return(0,i.jsx)(c.uz,{className:"playgroundEditor_TySg"},String(e))}function k(){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(f,{children:(0,i.jsx)(d.Z,{id:"theme.Playground.liveEditor",description:"The live editor label of the live codeblocks",children:"Live Editor"})}),(0,i.jsx)(v,{})]})}let C=e=>`${e};`;function _(e){let{children:n,transformCode:s,...t}=e,{siteConfig:{themeConfig:r}}=(0,u.Z)(),{liveCodeBlock:{playgroundPosition:a}}=r,o=x(),l=t.metastring?.includes("noInline")??!1;return(0,i.jsx)("div",{className:"playgroundContainer_6Ior",children:(0,i.jsx)(c.nu,{code:n?.replace(/\n$/,""),noInline:l,transformCode:s??C,theme:o,...t,children:"top"===a?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(y,{}),(0,i.jsx)(k,{})]}):(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(k,{}),(0,i.jsx)(y,{})]})})})}function N(e){return(0,i.jsx)("button",{type:"button",...e,style:{backgroundColor:"white",color:"black",border:"solid red",borderRadius:20,padding:10,cursor:"pointer",...e.style}})}let T={React:a,...a,...r};var I=s(55951),L=s(6324),B=s.n(L);let Z=/title=(?<quote>["'])(?<title>.*?)\1/,D=/\{(?<range>[\d,-]+)\}/,S={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},E={...S,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},M=Object.keys(S);function A(e,n){let s=e.map(e=>{let{start:s,end:t}=E[e];return`(?:${s}\\s*(${n.flatMap(e=>[e.line,e.block?.start,e.block?.end].filter(Boolean)).join("|")})\\s*${t})`}).join("|");return RegExp(`^\\s*(?:${s})\\s*$`)}function O(e){let{as:n,...s}=e,t=function(e){let n={color:"--prism-color",backgroundColor:"--prism-background-color"},s={};return Object.entries(e.plain).forEach(e=>{let[t,r]=e,i=n[t];i&&"string"==typeof r&&(s[i]=r)}),s}(x());return(0,i.jsx)(n,{...s,style:t,className:(0,o.Z)(s.className,"codeBlockContainer_jDV4",I.k.common.codeBlock)})}let U={codeBlockContent:"codeBlockContent_vx7S",codeBlockTitle:"codeBlockTitle_bdru",codeBlock:"codeBlock_Gebt",codeBlockStandalone:"codeBlockStandalone_i_cY",codeBlockLines:"codeBlockLines_FJaf",codeBlockLinesWithNumbering:"codeBlockLinesWithNumbering_FU9Q",buttonGroup:"buttonGroup_cUGO"};function R(e){let{children:n,className:s}=e;return(0,i.jsx)(O,{as:"pre",tabIndex:0,className:(0,o.Z)(U.codeBlockStandalone,"thin-scrollbar",s),children:(0,i.jsx)("code",{className:U.codeBlockLines,children:n})})}var W=s(50923);let P={attributes:!0,characterData:!0,childList:!0,subtree:!0};var $=s(7316);let q={codeLine:"codeLine_qRmp",codeLineNumber:"codeLineNumber_dS_J",codeLineContent:"codeLineContent_XF5l"};function G(e){let{line:n,classNames:s,showLineNumbers:t,getLineProps:r,getTokenProps:a}=e,l=function(e){let n=1===e.length&&"\n"===e[0].content?e[0]:void 0;return n?[{...n,content:""}]:e}(n),c=r({line:l,className:(0,o.Z)(s,t&&q.codeLine)}),d=l.map((e,n)=>(0,i.jsx)("span",{...a({token:e})},n));return(0,i.jsxs)("span",{...c,children:[t?(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("span",{className:q.codeLineNumber}),(0,i.jsx)("span",{className:q.codeLineContent,children:d})]}):d,(0,i.jsx)("br",{})]})}var H=s(44771);function z(e){return(0,i.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,i.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function F(e){return(0,i.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,i.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}let X={copyButtonCopied:"copyButtonCopied_OkN_",copyButtonIcons:"copyButtonIcons_OqsO",copyButtonIcon:"copyButtonIcon_PgCn",copyButtonSuccessIcon:"copyButtonSuccessIcon_bsQG"};function J(e){let{code:n,className:s}=e,[t,r]=(0,a.useState)(!1),l=(0,a.useRef)(void 0),c=(0,a.useCallback)(()=>{(0,H.Z)(n),r(!0),l.current=window.setTimeout(()=>{r(!1)},1e3)},[n]);return(0,a.useEffect)(()=>()=>window.clearTimeout(l.current),[]),(0,i.jsx)("button",{type:"button","aria-label":t?(0,d.I)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,d.I)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,d.I)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,o.Z)("clean-btn",s,X.copyButton,t&&X.copyButtonCopied),onClick:c,children:(0,i.jsxs)("span",{className:X.copyButtonIcons,"aria-hidden":"true",children:[(0,i.jsx)(z,{className:X.copyButtonIcon}),(0,i.jsx)(F,{className:X.copyButtonSuccessIcon})]})})}function Y(e){return(0,i.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,i.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}let V={wordWrapButtonIcon:"wordWrapButtonIcon_MQXS",wordWrapButtonEnabled:"wordWrapButtonEnabled_TBIH"};function K(e){let{className:n,onClick:s,isEnabled:t}=e,r=(0,d.I)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,i.jsx)("button",{type:"button",onClick:s,className:(0,o.Z)("clean-btn",n,t&&V.wordWrapButtonEnabled),"aria-label":r,title:r,children:(0,i.jsx)(Y,{className:V.wordWrapButtonIcon,"aria-hidden":"true"})})}function Q(e){var n;let{children:s,className:t="",metastring:r,title:c,showLineNumbers:d,language:u}=e,{prism:{defaultLanguage:h,magicComments:p}}=(0,m.L)(),g=(n=u??function(e){let n=e.split(" ").find(e=>e.startsWith("language-"));return n?.replace(/language-/,"")}(t)??h,n?.toLowerCase()),j=x(),f=function(){let[e,n]=(0,a.useState)(!1),[s,t]=(0,a.useState)(!1),r=(0,a.useRef)(null),i=(0,a.useCallback)(()=>{let s=r.current.querySelector("code");e?s.removeAttribute("style"):(s.style.whiteSpace="pre-wrap",s.style.overflowWrap="anywhere"),n(e=>!e)},[r,e]),o=(0,a.useCallback)(()=>{let{scrollWidth:e,clientWidth:n}=r.current;t(e>n||r.current.querySelector("code").hasAttribute("style"))},[r]);return!function(e,n){let[s,t]=(0,a.useState)(),r=(0,a.useCallback)(()=>{t(e.current?.closest("[role=tabpanel][hidden]"))},[e,t]);(0,a.useEffect)(()=>{r()},[r]),function(e,n){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:P,t=(0,W.zX)(n),r=(0,W.Ql)(s);(0,a.useEffect)(()=>{let n=new MutationObserver(t);return e&&n.observe(e,r),()=>n.disconnect()},[e,t,r])}(s,e=>{e.forEach(e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(n(),r())})},{attributes:!0,characterData:!1,childList:!1,subtree:!1})}(r,o),(0,a.useEffect)(()=>{o()},[e,o]),(0,a.useEffect)(()=>(window.addEventListener("resize",o,{passive:!0}),()=>{window.removeEventListener("resize",o)}),[o]),{codeBlockRef:r,isEnabled:e,isCodeScrollable:s,toggle:i}}(),w=(0,l.Z)(),b=(r?.match(Z)?.groups.title??"")||c,{lineClassNames:y,code:v}=function(e,n){let s=e.replace(/\n$/,""),{language:t,magicComments:r,metastring:i}=n;if(i&&D.test(i)){let e=i.match(D).groups.range;if(0===r.length)throw Error(`A highlight range has been given in code block's metastring (\`\`\` ${i}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);let n=r[0].className;return{lineClassNames:Object.fromEntries(B()(e).filter(e=>e>0).map(e=>[e-1,[n]])),code:s}}if(void 0===t)return{lineClassNames:{},code:s};let a=function(e,n){switch(e){case"js":case"javascript":case"ts":case"typescript":return A(["js","jsBlock"],n);case"jsx":case"tsx":return A(["js","jsBlock","jsx"],n);case"html":return A(["js","jsBlock","html"],n);case"python":case"py":case"bash":return A(["bash"],n);case"markdown":case"md":return A(["html","jsx","bash"],n);case"tex":case"latex":case"matlab":return A(["tex"],n);case"lua":case"haskell":return A(["lua"],n);case"sql":return A(["lua","jsBlock"],n);case"wasm":return A(["wasm"],n);case"vb":case"vba":case"visual-basic":return A(["vb","rem"],n);case"vbnet":return A(["vbnet","rem"],n);case"batch":return A(["rem"],n);case"basic":return A(["rem","f90"],n);case"fsharp":return A(["js","ml"],n);case"ocaml":case"sml":return A(["ml"],n);case"fortran":return A(["f90"],n);case"cobol":return A(["cobol"],n);default:return A(M,n)}}(t,r),o=s.split("\n"),l=Object.fromEntries(r.map(e=>[e.className,{start:0,range:""}])),c=Object.fromEntries(r.filter(e=>e.line).map(e=>{let{className:n,line:s}=e;return[s,n]})),d=Object.fromEntries(r.filter(e=>e.block).map(e=>{let{className:n,block:s}=e;return[s.start,n]})),u=Object.fromEntries(r.filter(e=>e.block).map(e=>{let{className:n,block:s}=e;return[s.end,n]}));for(let e=0;e<o.length;){let n=o[e].match(a);if(!n){e+=1;continue}let s=n.slice(1).find(e=>void 0!==e);c[s]?l[c[s]].range+=`${e},`:d[s]?l[d[s]].start=e:u[s]&&(l[u[s]].range+=`${l[u[s]].start}-${e-1},`),o.splice(e,1)}s=o.join("\n");let h={};return Object.entries(l).forEach(e=>{let[n,{range:s}]=e;B()(s).forEach(e=>{h[e]??=[],h[e].push(n)})}),{lineClassNames:h,code:s}}(s,{metastring:r,language:g,magicComments:p}),k=function(e){let{showLineNumbers:n,metastring:s}=e;return"boolean"==typeof n?n?1:void 0:"number"==typeof n?n:function(e){let n=e?.split(" ").find(e=>e.startsWith("showLineNumbers"));if(n)return n.startsWith("showLineNumbers=")?parseInt(n.replace("showLineNumbers=",""),10):1}(s)}({showLineNumbers:d,metastring:r});return(0,i.jsxs)(O,{as:"div",className:(0,o.Z)(t,g&&!t.includes(`language-${g}`)&&`language-${g}`),children:[b&&(0,i.jsx)("div",{className:U.codeBlockTitle,children:b}),(0,i.jsxs)("div",{className:U.codeBlockContent,children:[(0,i.jsx)($.y$,{theme:j,code:v,language:g??"text",children:e=>{let{className:n,style:s,tokens:t,getLineProps:r,getTokenProps:a}=e;return(0,i.jsx)("pre",{tabIndex:0,ref:f.codeBlockRef,className:(0,o.Z)(n,U.codeBlock,"thin-scrollbar"),style:s,children:(0,i.jsx)("code",{className:(0,o.Z)(U.codeBlockLines,void 0!==k&&U.codeBlockLinesWithNumbering),style:void 0===k?void 0:{counterReset:`line-count ${k-1}`},children:t.map((e,n)=>(0,i.jsx)(G,{line:e,getLineProps:r,getTokenProps:a,classNames:y[n],showLineNumbers:void 0!==k},n))})})}}),w?(0,i.jsxs)("div",{className:U.buttonGroup,children:[(f.isEnabled||f.isCodeScrollable)&&(0,i.jsx)(K,{className:U.codeButton,onClick:()=>f.toggle(),isEnabled:f.isEnabled}),(0,i.jsx)(J,{className:U.codeButton,code:v})]}):null]})]})}let ee=(t=function(e){let{children:n,...s}=e,t=(0,l.Z)(),r=a.Children.toArray(n).some(e=>(0,a.isValidElement)(e))?n:Array.isArray(n)?n.join(""):n;return(0,i.jsx)("string"==typeof r?Q:R,{...s,children:r},String(t))},function(e){return e.live?(0,i.jsx)(_,{scope:T,...e}):(0,i.jsx)(t,{...e})});function en(e){return(0,i.jsx)(ee,{...e})}}}]);