From 51c3d9d664fbaf51157c0b90c20b195a555194b9 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 4 May 2022 13:46:04 +0100 Subject: [PATCH] I18n: Developer documentation (#48415) * Docs: Add developer documentation for localisation * touch up docs * more assertive * add example for variable names * Add naming conventions * Rewrite 'what lingui does' section --- contribute/localisation.md | 194 +++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 contribute/localisation.md diff --git a/contribute/localisation.md b/contribute/localisation.md new file mode 100644 index 00000000000..97f4ed8d284 --- /dev/null +++ b/contribute/localisation.md @@ -0,0 +1,194 @@ +# Localisation + +Grafana uses the [LinguiJS](https://github.com/lingui/js-lingui) framework for managing translating phrases in the Grafana frontend. + +## tl;dr + +- Use `Go to {panel.title}` in code to add a translatable phrase +- Translations are stored in .po files in `public/locales/{locale}/messages.po` +- If a particular phrase is not available in the a language then it will fall back to English + +## How to add a new translation phrase + +1. Use one of `@lingui/macro`'s React components with the `id`, ensuring it conforms to the guidelines below, with the default english translation. e.g. + +```jsx +import { Trans } from @lingui/macro + +const SearchTitle = ({term}) => ( + + Results for {term} + +); +``` + +Prefer using the JSX components (compared to the plain javascript functions, see below) where possible for phrases. Many props can (and probably should) be changed to accept the `React.ReactNode` instead of `string` for phrases put into the DOM. + +Note that Lingui must be able to statically analyse the code to extract the phrase, so the `id` can not be dynamic. e.g. the following will not work: + +```jsx +const ErrorMessage = ({ id, message }) => There was an error: {message}; +``` + +2. Upon reload, the default English phrase will appear on the page. + +3. Before submitting your PR, run the `yarn i18n:extract` command to extract the messages you added into the `messages.po` file and make them available for translation. + +## How translations work in Grafana + +Grafana uses the [LinguiJS](https://github.com/lingui/js-lingui) framework for managing translating phrases in the Grafana frontend. It: + +- Marks up phrases within our code for extraction +- Extracts phrases into messages catalogues for translating in external systems +- "Compiles" the catalogues to a format that can be used in the website +- Manages the user's locale and putting the translated phrases in the UI + +### Phrase ID naming convention + +We set explicit IDs for phrases to make it easier to identify phrases out of context, and to track where they're used. IDs follow a naming scheme that includes _where_ the phrase is used. The exception is the rare case of single reoccuring words like "Cancel", but default to using a feature/phrase specific phrase. + +Message IDs are made of _up to_ three segments in the format `feature.area.phrase`. For example: + +- `dashboard.header.refresh-label` +- `explore.toolbar.share-tooltip` + +For components used all over the site, use just two segments: + +- `footer.update` +- `navigation.home` + +### Top-level provider + +In [AppWrapper.tsx](/public/app/AppWrapper.tsx) the app is wrapped with `I18nProvider` from `public/app/core/localisation.tsx` where the Lingui instance is created with the user's preferred locale. This sets the appropriate context and allows any component from `@lingui/macro` to use the translations for the user's preferred locale. + +### Message format + +Lingui uses the [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) for the phrases in the .po catalogues. ICU has special syntax especially for describing plurals across multiple languages. For more details see the [Lingui docs](https://lingui.js.org/ref/message-format.html). + +### Plain JS usage + +See [Lingui Docs](https://lingui.js.org/ref/macro.html#t) for more details. + +Sometimes you may need to translate a string cannot be represented in JSX, such as `placeholder` props. Use the `t` macro for this. + +```jsx +import { t } from "@lingui/macro" + +const placeholder = t({ + id: 'form.username-placeholder', + message: `Username` +}); + +return +``` + +While the `t` macro can technically be used outside of React functions (e.g, in actions/reducers), aim to keep all UI phrases within the React UI functions. + +## Examples + +See the [Lingui docs](https://lingui.js.org/ref/macro.html#usage) for more details. + +### Basic usage + +For fixed phrases: + +```jsx +import { Trans } from '@lingui/macro'; + +Hello user!; +``` + +You can include variables, just like regular JSX. Prefer using "simple" variables to make the extracted phrase easier to read for translators + +```jsx +import { Trans } from '@lingui/macro'; + +// Bad - translators will see: Hello {0} +Hello {user.name}!; + +// Good - translators will see: Hello {userName} +const userName = user.name; +Hello {userName}!; +``` + +Variables must be strings (or, must support calling `.toString()`, which we almost never want). + +```jsx +import { Trans } from '@lingui/macro'; + +// This will not work +const userName = user.name; +Hello {userName}!; + +// Instead, put the JSX inside the phrase directly +const userName = user.name; + + Hello {userName}! +; +``` + +### React components and HTML tags + +Both HTML tags and React components can be included in a phase. The Lingui macro will replace them with placeholder tags for the translators + +```js +import { Trans } from "@lingui/macro" + +const randomVariable = "variable" + + + Click to learn more. + + +// ↓ is transformed by macros into ↓ +, + + ]} +/> + +// ↓ is in the messages.po file like ↓ +msgid "page.explainer" +msgstr "Click <0>here to <1>learn more" +``` + +### Plurals + +See the [Lingui docs](https://lingui.js.org/ref/macro.html#id1) for more details. + +Plurals require special handling to make sure they can be translating according to the rules of each locale (which may be more complex that you think!). Use the `` component and specify the plural forms for the default language (English). The message will be extracted into a form where translators can extend it with rules for other locales. + +```js +import { Plural } from "@lingui/macro" + + + +// ↓ is transformed by macros into ↓ + + + +// sharedCount = 0 -> Not shared with anyone +// sharedCount = 1 -> Shared with one person +// sharedCount = 3 -> Shared with # people +``` + +### Date and time + +[Lingui has functions](https://lingui.js.org/ref/core.html#I18n.date) to format dates and times according to the convention to the user's preferred locale, based on the browser [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) API. However, as displaying dates and times is fundamental to Grafana, guidelines have not been established for this yet. + +## Documentation + +[Grafana's documentation](https://grafana.com/docs/grafana/latest/) is not yet open for translation and should be authored in English only.