formio-sfds
Version:
The Form.io theme for sf.gov
272 lines (255 loc) • 11.6 kB
HTML
<html lang="en">
<head>
<title>Localization | formio-sfds documentation</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="/sfgov/forms.css">
<link rel="stylesheet" href="https://unpkg.com/highlight.js/styles/github.css">
<style type="text/css">
.h-1em { height: 1em ; }
h2,
h3,
h4,
h5,
h6 {
position: relative;
}
main p {
margin: 0;
}
main p + p {
margin-top: 20px;
}
li + li {
margin-top: 5px;
}
table {
margin: 20px 0;
}
th,
td {
padding: 5px;
}
a[aria-hidden="true"] {
position: absolute;
right: 100%;
font-weight: normal;
text-decoration: none;
}
</style>
</head>
<body>
<div class="formio-sfds">
<div class="container p-2">
<h1 class="h3 mb-4">
<a href="/">formio-sfds@10.0.0</a> /
<a href="/docs/">Docs</a> /
Localization
</h1>
<main>
<h2 id="user-content-localization"><a href="#localization" aria-hidden="true" tabindex="-1"><span>#</span></a>Localization</h2>
<p>We use <a href="https://phrase.com">Phrase</a> to manage translations of both "generic" form strings and
content specific to each form. There are a couple of different workflows to be
aware of:</p>
<ul>
<li><a href="#translate-generic-strings-content">Translate generic strings</a></li>
<li><a href="#update-generic-string-translations-engineering">Update generic string translations</a></li>
<li><a href="#translate-a-form">Translate a specific form</a></li>
<li><a href="#manual-translations">Manually adding translations</a></li>
</ul>
<p>But first, it's important to understand how the whole thing works!</p>
<h3 id="user-content-how-it-works"><a href="#how-it-works" aria-hidden="true" tabindex="-1"><span>#</span></a>How it works</h3>
<p>There are a bunch of moving pieces involved in coordinating translations
between form.io, Phrase, and our theme:</p>
<ul>
<li>
<p>The formio.js library that we extend and patch with this theme uses a
localization tool called [i18next], and passes most of the "strings"
(individual chunks of text content, like the text of the "Next" and
"Previous" buttons, or the label of each form component) through its
translation function, <code>t()</code>.</p>
</li>
<li>
<p>Formio.js accepts an <code>i18n</code> option that can include translations. Its shape
is an object with language codes at the top level, and translations below. To
translate the "Next" button text in page navigation to Spanish, we would pass:</p>
<pre><code>{
<span>es</span>: {
<span>Next</span>: <span>'Siguiente'</span>
}
}</code></pre>
</li>
<li>
<p>In our templates we call the <code>t()</code> function with automatically generated IDs
("keys") for each unique string. The naming scheme is the component's key and
the "path" of the string within the component, separated by <code>.</code>. For
instance, the string ID for the label of a component with the key <code>email</code>
would be <code>email.label</code>. The <code>content</code> field of an HTML element <code>intro</code> could
be localized with the <code>intro.content</code> string.</p>
</li>
<li>
<p>We patch several of the third-party libraries that formio.js uses to render
more complex components:</p>
<ol>
<li>
<p>We import the Spanish and Chinese translations of <a href="https://flatpickr.js.org/">Flatpickr</a> directly
to localize date and time pickers. These are not customizable right now.</p>
</li>
<li>
<p>We patch the <code>customOptions</code> of each <a href="./autocomplete/#translation">autocomplete component</a>
to translate UI strings in <a href="https://github.com/jshjohnson/Choices#readme">Choices.js</a>.</p>
</li>
</ol>
</li>
</ul>
<h3 id="user-content-translate-generic-strings-content"><a href="#translate-generic-strings-content" aria-hidden="true" tabindex="-1"><span>#</span></a>Translate generic strings (content)</h3>
<ol>
<li>Translate the strings in the <a href="https://app.phrase.com/accounts/city-county-of-san-francisco/projects/form-io-generic-strings">generic strings Phrase project</a></li>
<li>Ask somebody with access to this repo to pull the new translations (see below)</li>
</ol>
<h3 id="user-content-update-generic-string-translations-engineering"><a href="#update-generic-string-translations-engineering" aria-hidden="true" tabindex="-1"><span>#</span></a>Update generic string translations (engineering)</h3>
<ol>
<li>Install the <a href="https://phrase.com/cli/">Phrase CLI tools</a></li>
<li>Check out this repo</li>
<li>Set <code>PHRASE_ACCESS_TOKEN</code> in your local environment (i.e. in <code>.env</code>)</li>
<li>Run <code>script/phrase pull</code> to get the new strings</li>
<li>If <code>git diff src/i18n</code> shows a diff, then commit the changes and <a href="../develop/#publishing">publish a new release</a></li>
</ol>
<h3 id="user-content-translate-a-form"><a href="#translate-a-form" aria-hidden="true" tabindex="-1"><span>#</span></a>Translate a form</h3>
<p>Before you can translate a form, you'll need to do some one-time setup in both Phrase and the form.io portal:</p>
<h4 id="user-content-phrase-setup"><a href="#phrase-setup" aria-hidden="true" tabindex="-1"><span>#</span></a>Phrase setup</h4>
<ol>
<li>
<p>Create a new Phrase project in the <a href="https://app.phrase.com/accounts/city-county-of-san-francisco/spaces">SF Digital Services workspace</a></p>
</li>
<li>
<p>Open the More ⌄ menu in the project navigation and select Project Settings</p>
<blockquote>
</blockquote>
</li>
<li>
<p>Select the API menu item on the left, and copy the Project ID:</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/97355530-44788280-1854-11eb-8db4-2d0534d9897d.png" alt="image of the Project ID field"></p>
</blockquote>
</li>
</ol>
<h4 id="user-content-formio-setup"><a href="#formio-setup" aria-hidden="true" tabindex="-1"><span>#</span></a>Form.io setup</h4>
<ol>
<li>
<p>Edit your form in the <a href="https://portal.form.io">form.io portal</a>, and</p>
</li>
<li>
<p>Click the gear icon in the form navigation to edit its settings:</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/97355914-dbddd580-1854-11eb-89bb-fde9cc30cebe.png" alt="image of the gear icon in form.io project settings"></p>
</blockquote>
</li>
<li>
<p>In the Custom Properties section, add a new entry with <code>phraseProjectId</code> in the "Key" field and the Phrase project ID that you copied in the "Value" field:</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/88114083-fa527780-cb67-11ea-98a1-b85273db617a.png" alt="image of the custom properties in form.io"></p>
</blockquote>
</li>
<li>
<p>Visit <a href="/api/strings?formUrl=%3CURL%3E">/api/strings?formUrl=<code><URL></code></a> where <code><URL></code> is your form.io data source URL</p>
</li>
<li>
<p>Save the JSON to your computer</p>
</li>
<li>
<p>Upload the JSON to your Phrase project:</p>
</li>
</ol>
<ul>
<li>
<p>Under the More ⌄ menu, select Upload file</p>
</li>
<li>
<p>Click Choose file and find the JSON file that you just saved</p>
</li>
<li>
<p>Choose <code>i18next (.json)</code> from the Format menu</p>
</li>
<li>
<p>Under Language, type <code>en</code> in the field under Use existing language</p>
<blockquote>
</blockquote>
</li>
<li>
<p>Click the Upload button</p>
</li>
<li>
<p>Confirm that strings were loaded and click on Translate imported keys to view them:</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/97367809-00db4400-1867-11eb-8115-dca7ca108504.png" alt="image"></p>
</blockquote>
</li>
</ul>
<ol>
<li>Visit <code>https://formio-sfds.herokuapp.com/api/translate?url=<URL></code>, where <code><URL></code> is the URL of your form <em>on sf.gov</em></li>
<li>When the form loads, you should see a modal dialog to log in to Phrase</li>
</ol>
<p>Once you've logged in, you should see a blue bar across the bottom and pencil icon markers above each piece of translatable form content:</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/88839471-f3db8580-d18f-11ea-8121-e0ce158ca274.png" alt="Phrase in-context editor screenshot"></p>
</blockquote>
<p>Translation updates can be packaged up in a "release" by adding or updating the form's <code>phraseProjectVersion</code> custom property (in the same place that you added <code>phraseProjectId</code> above):</p>
<blockquote>
<p><img src="https://user-images.githubusercontent.com/113896/97610569-4d8f5e00-19d2-11eb-9f22-67abb29dcab7.png" alt="image"></p>
</blockquote>
<p>Please use <a href="https://semver.org">semantic versioning conventions</a> to track the types of changes:</p>
<ul>
<li>Patch versions (<code>1.0.0</code> → <code>1.0.1</code>) for translation updates and other "fixes", like spelling</li>
<li>Minor versions (<code>1.0.1</code> → <code>1.1.0</code>) when new keys and/or translations are added</li>
<li>Major versions (<code>1.0.0</code> → <code>2.0.0</code>) when keys are deleted</li>
</ul>
<h4 id="user-content-manual-translations"><a href="#manual-translations" aria-hidden="true" tabindex="-1"><span>#</span></a>Manual translations</h4>
<p>Because of how automatically generated translation keys (the unique IDs of each
translatable string) are generated automatically and how the API that
"extracts" strings from each form works, there may be some situations that call
for manually adding strings in Phrase. Here are some tips:</p>
<ol>
<li>
<p>First, try adding a string with a key in the form <code>{component}.{path}</code>, where
<code>{component}</code> is the component key and <code>{path}</code> is the "path" of the
component field that you're trying to translate: <code>label</code>, <code>description</code>, etc.</p>
</li>
<li>
<p>If that doesn't work, please <a href="https://github.com/SFDigitalServices/formio-sfds/issues/new">file an issue</a>!</p>
</li>
<li>
<p>If your form has been heavily modified on form.io, it's possible that string
keys no longer map to the right components. Try re-generating the form
strings and importing the JSON into Phrase again.</p>
</li>
<li>
<p>If all else fails, you should still be able to add keys for the English
string and translate those. For instance, if a bug is preventing the label
of a field with the label "Your address" from translating, you should still
be able to target it by adding translations for a "Your address" Phrase key.</p>
</li>
</ol>
<p>When in doubt, drop into <strong>#topic-translations</strong> on Slack and ask for help! 💪</p>
</main>
<div class="bg-grey-1 p-2 round-1 mt-4 d-flex flex-justify-between">
<div class="mr-2">
<a href="https://github.com/SFDigitalServices/formio-sfds/edit/main/docs/localization.md">
Edit this page on GitHub
</a>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/formiojs@4.14.8/dist/formio.full.min.js"></script>
<script src="/dist/formio-sfds.standalone.js"></script>
<script>
// fix images with height attributes that get
// `height: auto` from drupal.css
for (const img of document.querySelectorAll('img[height]')) {
img.setAttribute('style', `height: ${img.getAttribute('height')}px;`)
}
</script>
</body>
</html>