[{"data":1,"prerenderedAt":29},["ShallowReactive",2],{"doc-languages":3},{"html":4,"headings":5},"\u003Cp>Show onboarding, legal pages, emails and lead forms in your users' language. Write each once,\nadd the languages you serve, and either translate with AI or write the words\nyourself. Anything a translation leaves out is shown in the original language, so\na half-translated screen never goes blank.\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>\u003C\u002Fth>\n\u003Cth>How languages work\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fonboarding#languages\">Onboarding\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One flow, a translation per language of its name, titles, bodies, button labels and image descriptions. Apps ask for one with \u003Ccode>?locale=\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Flegal#other-languages\">Legal documents\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One linked document per language, each with its own page, versions and publish state.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felrumo\u002Fhelp-bites-oss\u002Fblob\u002Fmain\u002Fdocs\u002FEMAIL.md#languages\" target=\"_blank\" rel=\"noopener\">Emails\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One template, a translation per language of its words. A send names the language with \u003Ccode>locale\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Fforms#languages\">Lead forms\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One form, a translation per language of its words and each field's label, placeholder and options. Visitors get theirs; errors and emails follow.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"#a-contacts-language\">Campaigns, sequences and welcome emails\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Each contact has a language; every recipient gets the translation of the template closest to it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>A language is a tag in the usual form: \u003Ccode>es\u003C\u002Fcode>, \u003Ccode>fr\u003C\u002Fcode>, \u003Ccode>pt-BR\u003C\u002Fcode>, \u003Ccode>zh-Hant\u003C\u002Fcode>. HelpBites\nwrites them in one spelling (\u003Ccode>pt_br\u003C\u002Fcode> becomes \u003Ccode>pt-BR\u003C\u002Fcode>) and matches the nearest one:\na request for \u003Ccode>es-MX\u003C\u002Fcode> gets \u003Ccode>es\u003C\u002Fcode> when there is no \u003Ccode>es-MX\u003C\u002Fcode>, and a language that does\nnot exist gets the original. The panel offers the common ones and takes any tag.\u003C\u002Fp>\n\u003Ch2 id=\"translating-with-ai\">Translating with AI\u003Ca class=\"heading-anchor\" href=\"#translating-with-ai\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Every place that has languages has a \u003Cstrong>Translate with AI\u003C\u002Fstrong> button, and the same\naction over the API:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Route\u003C\u002Fth>\n\u003Cth>\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u002Ftranslate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;locales&quot;: [&quot;es&quot;, &quot;fr&quot;], &quot;overwrite&quot;?: false }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Flegal\u002Fdocuments\u002F{id}\u002Ftranslate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;locale&quot;: &quot;es&quot; }\u003C\u002Fcode>: creates a new linked draft\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Femails\u002F{id}\u002Ftranslate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;locales&quot;: [&quot;es&quot;, &quot;fr&quot;], &quot;overwrite&quot;?: false }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Flead-forms\u002F{id}\u002Ftranslate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ &quot;locales&quot;: [&quot;es&quot;, &quot;fr&quot;], &quot;overwrite&quot;?: false }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>It uses the same AI as every other HelpBites feature: the workspace's own provider\nwhen you set one (Settings, AI), otherwise the server's gateway\n(\u003Ccode>LITELLM_API_BASE_URL\u003C\u002Fcode>), where it spends AI credits on the hosted service. Without\neither, it answers \u003Ccode>501\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>What keeps the result safe to save:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>The model only sees a numbered list of strings (never your project's data) and is\ntold that they are text to translate, not instructions.\u003C\u002Fli>\n\u003Cli>It must return the list whole. Each string must keep its \u003Ccode>{{placeholders}}\u003C\u002Fcode>,\nlinks and HTML tags, or that string is left in the original language rather than\nsaved broken. Strings with no words in them (a lone placeholder, a URL) are not\nsent at all.\u003C\u002Fli>\n\u003Cli>Strings that already have a translation are kept, so your edits survive a re-run.\n\u003Ccode>overwrite: true\u003C\u002Fcode> translates everything again (the panel's \u003Cem>Translate everything\nagain\u003C\u002Fem>).\u003C\u002Fli>\n\u003Cli>At most 8 languages per request, 400 strings per call, and 20 translations kept\nper flow, template or form. Requests are rate limited per operator.\u003C\u002Fli>\n\u003Cli>Nothing reaches your users until you publish: onboarding translations go out with\nthe next publish, translated legal documents are drafts, and emails and lead\nforms use a translation as soon as it is saved.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>AI translation is a draft, not a final text. Read it, and for legal documents have\na lawyer review it.\u003C\u002Fp>\n\u003Ch2 id=\"showing-a-language\">Showing a language\u003Ca class=\"heading-anchor\" href=\"#showing-a-language\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Ch3 id=\"web\">Web\u003Ca class=\"heading-anchor\" href=\"#web\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>\u003Ccode>onboarding.js\u003C\u002Fcode> asks for the visitor's language on its own: your \u003Ccode>&lt;html lang&gt;\u003C\u002Fcode>, then\nthe browser's. Set it yourself with \u003Ccode>data-locale=&quot;es&quot;\u003C\u002Fcode> or \u003Ccode>locale\u003C\u002Fcode> in \u003Ccode>init\u003C\u002Fcode>. The\nbuttons' default words (&quot;Continue&quot;, &quot;Get started&quot;, &quot;Back&quot;, &quot;Skip&quot;) come in English,\nSpanish, Catalan, French, German, Italian, Portuguese, Japanese, Korean and\nChinese; pass \u003Ccode>labels\u003C\u002Fcode> for any other language. Right-to-left languages set the\ncard's direction.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-locale\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"es\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Ccode>forms.js\u003C\u002Fcode> does the same for \u003Ca href=\"\u002Fdocs\u002Fforms#languages\">lead forms\u003C\u002Fa> (\u003Ccode>data-locale\u003C\u002Fcode>), and\nthe hosted form page takes \u003Ccode>?locale=\u003C\u002Fcode> or the browser's language.\u003C\u002Fp>\n\u003Ch3 id=\"swift\">Swift\u003Ca class=\"heading-anchor\" href=\"#swift\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>The onboarding snippet requests the user's first language\n(\u003Ccode>Locale.preferredLanguages\u003C\u002Fcode>), or the one you pass:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>ContentView()\n  .helpBitesOnboarding(host: URL(string: \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\"\u003C\u002Fspan>)!, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, flow: \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>, locale: \u003Cspan class=\"shj-syn-str\">\"es\"\u003C\u002Fspan>)\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Its default button words are your app's own strings, so they follow your app's\nlocalisation. The lead form snippet takes \u003Ccode>locale\u003C\u002Fcode> the same way.\u003C\u002Fp>\n\u003Ch3 id=\"api\">API\u003Ca class=\"heading-anchor\" href=\"#api\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-cmnt\"># Onboarding: the translation, or the flow's own language when there is none\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fonboarding\u002FPROJECT_PUBLIC_KEY\u002Fwelcome?locale=es\"\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># A lead form: its words in that language, and which ones it has\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fforms\u002FPROJECT_PUBLIC_KEY\u002Fwaitlist?locale=es\"\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-cmnt\"># → { \"form\": { \"locale\": \"es\", \"locales\": [\"en\", \"es\"], … } }\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># A legal document and where its other languages are\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Flegal\u002FPROJECT_PUBLIC_KEY\u002Fprivacy-es\n\u003Cspan class=\"shj-syn-cmnt\"># → { \"document\": { \"locale\": \"es\", …, \"translations\": [{ \"locale\": \"en\", \"title\": \"Privacy Policy\", \"path\": \"\u002Flegal\u002F…\u002Fprivacy\" }] } }\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># An email, in each recipient's language\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Femails\u002Fsend \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES_SECRET\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Content-Type: application\u002Fjson\"\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"template\": \"welcome\", \"to\": [{ \"email\": \"ada@example.com\", \"locale\": \"es\" }, { \"email\": \"bob@example.com\", \"locale\": \"fr\" }] }'\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Build your own screens on these when the drop-in script and snippet are not what\nyou want; the answers say which language you got (\u003Ccode>locale\u003C\u002Fcode>), so you can pick your\nown words around them.\u003C\u002Fp>\n\u003Ch2 id=\"a-contacts-language\">A contact's language\u003Ca class=\"heading-anchor\" href=\"#a-contacts-language\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Every \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felrumo\u002Fhelp-bites-oss\u002Fblob\u002Fmain\u002Fdocs\u002FCONTACTS.md\" target=\"_blank\" rel=\"noopener\">contact\u003C\u002Fa> can have a \u003Ccode>language\u003C\u002Fcode> (\u003Ccode>es\u003C\u002Fcode>, \u003Ccode>pt-BR\u003C\u002Fcode>): the language to write\nto them in. It is filled in, in this order of trust:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>by your app, with the \u003Ccode>locale\u003C\u002Fcode> claim of the \u003Ca href=\"\u002Fdocs\u002Fconnect#2-mint-identity-tokens-on-your-server\">identity token\u003C\u002Fa>.\nIt only fills a gap: a language you set by hand stays;\u003C\u002Fli>\n\u003Cli>by you: in the contact's page (\u003Cstrong>Language\u003C\u002Fstrong>), over the API (\u003Ccode>language\u003C\u002Fcode> on\n\u003Ccode>POST \u002Fv1\u002Fcontacts\u003C\u002Fcode> and \u003Ccode>PATCH \u002Fv1\u002Fcontacts\u002F:id\u003C\u002Fcode>, empty clears it), or from a CSV\ncolumn or a synced column (the \u003Cstrong>Language\u003C\u002Fstrong> mapping; a value that is not a language\ntag is left out, not an error);\u003C\u002Fli>\n\u003Cli>by a \u003Ca href=\"\u002Fdocs\u002Fforms\">lead form\u003C\u002Fa>: the first language of the visitor's browser\n(\u003Ccode>Accept-Language\u003C\u002Fcode>), when the contact has none yet.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Campaigns, sequence steps and a lead form's welcome email are one design with the\ntranslations its template has. Each recipient gets the best match for their language:\nthe exact tag, then the same language (\u003Ccode>es-MX\u003C\u002Fcode> gets \u003Ccode>es\u003C\u002Fcode>), then the template's own\nlanguage, which is also what contacts without a language get. People are grouped by the\nlanguage they receive, so one campaign sends one message per person in as many\nlanguages as the audience needs.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>The campaign's own \u003Cstrong>Subject\u003C\u002Fstrong> is written in the template's language, so a translation\nthat has its own subject replaces it for those recipients. A translation without one\nkeeps the campaign's subject.\u003C\u002Fli>\n\u003Cli>A language is only offered once it has words: an empty language added in the editor\nis skipped.\u003C\u002Fli>\n\u003Cli>Every translation must keep the unsubscribe link and your postal address in its\nfooter. A campaign (or a sequence being activated) with a translation that lost\neither is refused, with the language named.\u003C\u002Fli>\n\u003Cli>The design is frozen with its translations when a campaign starts sending, like the\nrest of it, so translating the template later changes nothing already going out.\u003C\u002Fli>\n\u003Cli>\u003Ccode>{{contact.language}}\u003C\u002Fcode> is a placeholder in any template.\u003C\u002Fli>\n\u003Cli>Segments can ask for it: \u003Ccode>language is es\u003C\u002Fcode> (\u003Cstrong>Language\u003C\u002Fstrong> in the segment builder). A\nsegment of \u003Ccode>language is es\u003C\u002Fcode> that you send to a template without Spanish still receives\nthe template's own language.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"what-is-not-translated\">What is not translated\u003Ca class=\"heading-anchor\" href=\"#what-is-not-translated\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Emails you send as your own \u003Ccode>html\u003C\u002Fcode>, a \u003Ccode>document\u003C\u002Fcode> or a \u003Ccode>message\u003C\u002Fcode>: write them in the\nlanguage you send.\u003C\u002Fli>\n\u003Cli>Blog posts sent as a newsletter: a post is written in one language, so everyone\ngets it as written.\u003C\u002Fli>\n\u003Cli>Mail HelpBites sends by itself (verification codes, conversation replies, digests)\nand the chat, feedback and changelog embeds are in the project's one language.\u003C\u002Fli>\n\u003Cli>The send API does not read a contact's language: pass \u003Ccode>locale\u003C\u002Fcode> on the request or on\neach recipient (campaigns and sequences do read it).\u003C\u002Fli>\n\u003C\u002Ful>\n",[6,10,13,17,20,23,26],{"id":7,"text":8,"level":9},"translating-with-ai","Translating with AI",2,{"id":11,"text":12,"level":9},"showing-a-language","Showing a language",{"id":14,"text":15,"level":16},"web","Web",3,{"id":18,"text":19,"level":16},"swift","Swift",{"id":21,"text":22,"level":16},"api","API",{"id":24,"text":25,"level":9},"a-contacts-language","A contact's language",{"id":27,"text":28,"level":9},"what-is-not-translated","What is not translated",1792677543076]