blob: d4c1e26aeb9deac53b14fcf2378aae721947387d [file] [log] [blame] [view]
# Localization
## How to add a localizable string
When you introduce a new UI string or modify an existing one that will be
displayed to the users, or remove a string that is localized, follow these steps
so that it can be localized.
[TOC]
### Adding a string
Before proceeding, make sure you know the different
[localization APIs](#what-are-the-l10n-apis) and know which one you should use.
Code example:
```js
import * as i18n from '../i18n/i18n.js';
// at the top of example.js file, after import statements
const UIStrings = {
/**
* @description A string that is already added
*/
alreadyAddedString: 'Someone already created a "UIStrings = {}" and added this string',
/**
* @description This is an example description for my new string
*/
addThisString: 'The new string I want to add',
/**
* @description This is an example description for my new string with placeholder
* @example {example for placeholder} PH1
*/
addAnotherString: 'Another new string I want to add, with {PH1}',
};
const str_ = i18n.i18n.registerUIStrings('example.js', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
// ....
// in example.js file, where you want to call the string
const message1 = i18nString(UIStrings.addThisString);
console.log(message1); // The new string I want to add
const message2 = i18nString(UIStrings.addAnotherString, {PH1: 'a placeholder'});
console.log(message2); // Another new string I want to add, with a placeholder
```
1. If there is already `UIStrings = {}` declared in the file, add your string
to it. If there isn't `UIStrings = {}` in the file, create one and add your
string, also register the new UIStrings into the `en-US.json` by adding:
1. `const str_ = i18n.i18n.registerUIStrings({the current fileName.js,
relative to front_end}, UIStrings);`
1. `const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);`
2. Add description and examples for placeholder(if any):
1. To specify the description, use `@description …` `@description This is
an example description for my new string`
2. To specify an example for placeholder, use `@example {…} …` `@example
{example for placeholder} PH1`
3. To distinguish messages with the same content, use `@meaning …`
`@meaning This is an example meaning to differentiate this message from
the other message with same content`
3. Make sure your string is localizable:
1. Do not assume word order by using concatenation. Use the whole string. ❌
`javascript 'Add' + 'breakpoint'` ✔️ `javascript 'Add breakpoint'` or ❌
`javascript let description = 'first part' if (condition) description +=
' second part'` ✔️ `javascript let description if (condition)
description = 'first part second part' else description = 'first part'`
2. Use placeholder over concatenation. This is so that the translators can
adjust variable order based on what works in another language. For
example: ❌ `javascript 'Check ' + title + ' for more information.'` ✔️
`javascript 'Check {PH1} for more information.', {PH1: title}`
3. If your string contains <b>leading or trailing white space</b>, it's
usually an indication that it's half of a sentence. This decreases
localizability as it's essentially concatenating. Modify it so that it
doesn't contain leading or trailing white space anymore if you can.
4. <b>Backticks</b> are only used for the text that should not be
localized. They cannot be escaped as part of the string. Check if there
are something should not be localized (see
[locked terms](#phrases-that-are-fully-locked) for more details).
❌ Not localized
- Numbers: 1, 1.23, 1.2e3, etc.
- Application data: error codes, enums, database names, rgba, urls,
etc.
✔️ Can be localized
- Words and sentences
- Punctuation
- Units of measurement: kb/s, mph, etc.
4. The following commands would add the new strings to `en-US.json`:
- `git cl presubmit --upload`, or
- `node third_party/i18n/collect-strings.js` under the DevTools src folder
5. Strings containing possible plurals have a special format in ICU. This is
because plurals work quite differently in other languages, e.g. special
forms for two or three items.
❌ `javascript if (count === 1) { str = '1 breakpoint'; } else { str = '{n}
breakpoints', {n: count}; }`
✔️ `javascript '{n, plural, =1 {# breakpoint} other {# breakpoints}}', {n:
count};`
- '#' is replaced with the value of `n`
- 'n' is a naming convention, but any name can be used
- Nesting placeholders inside of plurals is allowed
- Put the entire string within the plural switch, e.g. `{# breakpoints
were found}`, not `{# breakpoints} were found`
- Always provide the `=1` and the `other` case, even if they are the same
for English.
### Modifying a string
1. Update the string you want to modify in `UIStrings`
2. Update the description and placeholders of the string if necessary
### Removing a string
1. Remove your string and the metadata from `UIStrings`
## What are the l10n APIs?
Access localized strings in the DevTools frontend using the following
localization calls.
### i18nString
The basic API to make a string (with or without placeholder) localizable. The
first argument is the string reference in `UIStrings` The second argument is an
object for placeholders (if any)
```js
// at the top of example.js file, after import statements
const UIStrings = {
/**
* @description This is an example description for my new string with placeholder
* @example {example for placeholder} PH1
* @example {example 2 for placeholder 2} PH2
*/
addAnotherString: 'Another new string I want to add, with {PH1} and {PH2}',
};
message = i18nString(UIStrings.addAnotherString, {PH1: 'a placeholder', PH2: 'another placeholder'});
```
### i18nLazyString
The `i18nString` function returns the translated string, with placeholders
resolved. To do this, it needs access to the translated strings for the user's
locale, which are not available until after DevTools has finished starting up.
Calls to `i18nString` in the module scope will therefore fail when the module is
imported.
```js
// Fails because i18nString runs at module-import time.
Common.Settings.registerSettingExtension({
category: Common.Settings.SettingCategory.CONSOLE,
title: i18nString(UIStrings.groupSimilarMessagesInConsole),
...
function notTopLevel() {
console.log(extension.title);
}
```
`i18nLazyString` fixes this problem by providing the same API, but returning a
closure that returns a `LocalizedString`. It can be used in top-level calls;
just make sure use-sites know it's a function now.
```js
// Works because i18nLazyString defers the loading of the translated string until later.
Common.Settings.registerSettingExtension({
category: Common.Settings.SettingCategory.CONSOLE,
title: i18nLazyString(UIStrings.groupSimilarMessagesInConsole),
...
// Note we need to call title() now.
function notTopLevel() {
console.log(extension.title());
}
```
### i18n.i18n.getFormatLocalizedString
This call returns a **span element**, not a string. It is used when you want to
construct a DOM element with a localizable string, or localizable content that
contains some other DOM element.
```js
// Create the string in UIString
/**
*@description Message in Coverage View of the Coverage tab
*@example {reload button icon} PH1
*@example {record button icon} PH2
*/
clickTheRecordButtonSToStart: 'Click the reload button {PH1} to reload or record button {PH2} start capturing coverage.',
// Element with localizable content containing two DOM elements that are buttons
const reloadButton = UI.createInlineButton(UI.Toolbar.createActionButton('coverage.start-with-reload'));
const recordButton = UI.createInlineButton(UI.Toolbar.createActionButton(this._toggleRecordAction));
message = i18n.i18n.getFormatLocalizedString(str_, UIStrings.clickTheReloadButtonSToReloadAnd, {PH1: reloadButton, PH2:recordButton });
```
### i18n.i18n.lockedString
This call is a named cast. Use it in places where a localized string is expected
but the term you want to use does not require translation. Instead of locking
the whole phrase or using a placeholder-only phrase, use `lockedString`.
```javascript
someFunctionRequiringALocalizedString(i18n.i18n.lockedString('HTTP'));
```
## How to write good descriptions
Good descriptions can improve localizability by providing more context to the
translators. There are some details that are very important to have in other
languages!
**Good description**:
```js
const UIStrings = {
/**
* @description Tooltip text that appears when hovering over the 'Focusable' attribute name under the Computed Properties section in the Accessibility pane of the Elements pane.
*/
computedPropertyTooltip: 'If true, this element can receive focus.',
};
```
**Bad description**:
```js
const UIStrings = {
/**
* @description Elements pane 'Focusable' tooltip.
*/
computedPropertyTooltip: 'If true, this element can receive focus.',
};
```
### What information should I provide in the message description?
- The type of UI element where the text is displayed. Is it regular text, a
label, button text, a tooltip, a link, or an accessible label? Button text
is often imperative i.e. a command to do something, which is important to
know in some languages.
- *When*: What triggers the string and/or what is the result? What page or
text comes before and after? e.g. "Status text while waiting for X", "Shown
when the audit is finished and X error was encountered".
- What do the placeholders stand for? Placeholder examples are sent to
translators, but extra information in the description will help too. e.g.
"Total time in ms that the profile took to complete", "The CSS property name
that is being edited"
- Is this a verb or a noun? Many words in English can be both, e.g. 'request',
'address', 'change', 'display', 'increase'. Particularly if the string is
short, this can be hard to guess. If it's an adjective, what does it refer
to? This is important for inflection in some languages, where the ending of
the adjective must change for gender or case.
- Explain or name any complex terms, e.g. "Trust Tokens are a web API -
https://web.dev/trust-tokens/"
- Where is the text located? e.g. A table header in the Sources panel, a
context-menu item in the Network panel. Many strings in the code base have
*only* the location, which is not the most important context.
## How to prevent a term being localized
Any text within the backticks will not be translated. For example, if the
'robots.txt' in string 'Requesting for robots.txt ...' should not be translated:
```javascript
// in example.js file
import * as i18n from '../i18n/i18n.js';
const UIStrings = {
/**
* @description Example description. Note: "robots.txt" is a canonical filename and should not be translated.
*/
requestMessage: 'Requesting for `robots.txt` ...',
};
const str_ = i18n.i18n.registerUIStrings('example.js', UIStrings);
const message = i18nString(UIStrings.requestMessage);
```
The string will rendered with robots.txt not translated and without the
backticks around it `js 'Requesting for robots.txt ...'`
### Phrases that are fully locked
Any text that is fully locked should not go into the UIStrings object. To make
your intention clear or to make TypeScript happy, there are two methods
`i18n.i18n.lockedString` and `i18n.i18n.lockedLazyString` that can be used
instead of having fully locked phrases via `i18nString`.
### What should not be localized?
In general, branding related terms and code snippets are the ones to look for,
and Sometimes some technical terms. Some examples:
**Brandings:** Lighthouse, GitHub, DevTools, Chrome Data Saver, Safari,
BlackBerry Z30, Kindle Fire HDX, Pixel 2, Microsoft Lumia 550
**Code snippets:** localhost:9229, console.clear(), --memlog=all, url:a.com
**Technical terms:** DOM, DIV, aria...