Test Internationalization API¶
Overview¶
The purpose of test internationalization (i18n) API is to have localized text on UI of each pytest, and user can choose between different languages on UI.
Quick start¶
A typical workflow to add or modify text in test:
Depends on where the text is used, use language-specific method to mark the text:
Python: Use
cros.factory.test.i18n._()
:from cros.factory.test.i18n import _ from cros.factory.test import test_ui class SomeTest(unittest.TestCaseWithUI): def setUp(self): self.ui.SetState(_('Some displayed text and should be translated.'))JavaScript: Use
window._()
(This is automatically injected to test iframe by Goofy):const label = _('Some displayed text and should be translated.') window.template.appendChild(cros.factory.i18n.i18nLabelNode(label))Static HTML: Use
<i18n-label>
:<test-template> <i18n-label>Some displayed text and should be translated.</i18n-label> </test-template>JSON test list items: Prefix the text with
"i18n! "
:{ "definitions": { "SomeTest": { "pytest_name": "some_test", "args": { "html": "i18n! Some displayed text and show be translated." } } } }
In chroot under
~/trunk/src/platform/factory/po
, run:make update
Please refer to Manage translation files for detail if code in board overlay is modified, or translation of new locale should be added.
Edit the translation files
~/trunk/src/platform/factory/po/*.po
. Currently there’s only one filezh-CN.po
that needs to be edited.The format of these files is in GNU gettext PO Files.
Find the line similar to lines below, and add translation to it:
#: ../py/test/pytests/my_awesome_test.py msgid "Some displayed text and should be translated." msgstr "The translated text, in zh-CN.po this should be Simplified Chinese"
Also remove all
#, fuzzy
lines, and remove all unused lines at the end of file started with#~
.The po file would be bundled together with the code when factory toolkit is made.
Run
make update
again to make sure the po file is formatted correctly.Refer to language specific section on how to use the i18n API to display the translated text: I18n in Python pytest, I18n in JavaScript, I18n in static HTML and I18n in JSON test list. (Actually, this should already be done together with step 1.)
I18n in Python pytest¶
All literal strings that need to be translated should be passed as the first
argument of _()
.
The return value of _()
is a translation dict, that is, a plain
python dict
with locale name as key, and translated string as value.
For example, the value of _('Cancel')
is {'en-US': 'Cancel',
'zh-CN': '取消'}
. The returned translation dict can be treated as an opaque
object most of the time.
Format string in i18n text¶
When there are keyword argument passed to _()
, the function would
do string format similar to Python str.format()
. If string format is
always needed even there’s no keyword argument passed, use
StringFormat()
. For example:
self.ui.SetState(
_('In test {test}, run {run_id}', test=_('Some test name'), run_id=1))
if x_exists:
format_string = _('{{x}} = {x}')
kwargs = {'x': x}
else:
format_string = _('{{x}} is not here!')
kwargs = {}
self.ui.SetState(i18n.StringFormat(format_string, **kwargs))
Display the i18n text on UI¶
To display the i18n text on UI, for methods that manipulate HTML in
StandardUI
(SetHTML()
,
AppendHTML()
, SetTitle()
,
SetState()
,
SetInstruction()
), the argument html
accepts
either a single string as HTML, a single translation dict, or an arbitrary
nested list of either HTML string or translation dict. For example:
self.ui.SetTitle(_('Some text'))
# There's no need to "concatenate" the text.
button = ['<button>', _('Click me!'), '</button>']
self.ui.SetState(['<div>', _('A button here: '), button, '</div>'])
Internationalized test argument¶
Sometimes, test need to have arguments that accepts internationalized text. Use
I18nArg
instead of
cros.factory.utils.arg_util.Arg
in this case.
User can pass a plain string, or a translation dict to the argument. The
argument in self.args is either None
or a translation dict no matter what
the user passed in.
For example:
from cros.factory.test.i18n import _
from cros.factory.test.i18n import arg_utils as i18n_arg_utils
from cros.factory.test import test_ui
class MyTest(test_ui.TestCaseWithUI):
ARGS = [
i18n_arg_utils.I18nArg('text', 'Some text', default=_('Default text'))
]
def setUp(self):
self.ui.SetState(['text: ', self.args.text])
Manipulating i18n text¶
There are several utility functions for transforming and combining translation dict, for example:
i18n.StringFormat('{a}[{b}]', a=1, b=_('Cancel'))
# => {'en-US': '1[Cancel]', 'zh-CN': '1[取消]'}
i18n.HTMLEscape({'en-US': '&<>', 'zh-CN': '&<>'})
# => {'en-US': '&<>', 'zh-CN': '&<>'}
See Method reference for detail.
I18n in JavaScript¶
All literal strings that need to be translated should be passed as the first
argument of window._()
.
The i18n API is under the namespace cros.factory.i18n()
, and is
similar to Python API with three twists:
The method names start with lowercase letter. For example,
cros.factory.i18n.stringFormat()
instead ofStringFormat()
.Since JavaScript doesn’t kave keyword arguments,
window._()
support an additional argument of mapping from name to values. For example:_('Test {test}, run {id}', {test: _('some test'), id: 1})
Since there’s no standarized “display” methods in JavaScript, two additional methods are introduced:
cros.factory.i18n.i18nLabel()
, which takes a string or translation dict, and returns agoog.html.SafeHtml()
from closure library.cros.factory.i18n.i18nLabelNode()
, which takes a string or translation dict, and returns aNode()
that can be inserted to document directly.
Note that the first argument of these two functions do not need to be marked by
window._()
. For example:const div = document.getElementById('some-div') goog.dom.safe.setInnerHtml(cros.factory.i18n.i18nLabel('some text')) goog.dom.safe.appendChild(cros.factory.i18n.i18nLabelNode('other text')) goog.dom.safe.appendChild( cros.factory.i18n.i18nLabelNode( _('Test {test}, run {id}', {test: _('some test'), id: 1})))
I18n in static HTML¶
Use <i18n-label>
for text element that need to be translated. The
i18n-label
is a custom HTML tag that acts like a span, and styles can be
applied on it like a span. For example:
<i18n-label id="some-label" style="color: red">Some red text</i18n-label>
would show a red text according to current selected language.
Note that all continuous space characters inside the tag would be replaced by one space, and leading / trailing spaces are removed, so the following two snippets are equivalent:
<i18n-label>I am some text</i18n-label>
<i18n-label>
I am
some
text
</i18n-label>
I18n in JSON test list¶
All string in either label
or args
can be prefixed by "i18n! "
to
make it an internationalized text. Those string would be extracted to po files
automatically, and pytest can use I18nArg
to handle this kind of
argument. See Internationalized test argument for detail.
Example:
{
"definitions": {
"MyTest": {
"pytest_name": "my_test",
"label": "i18n! My Special Test",
"args": {
"text": "i18n! I'm an argument!",
"other_arg": {
"something": ["i18n! Transform is done recursively like eval!."]
}
}
}
}
}
Since the translation dict is a normal dict
, a dict can be passed
directly from JSON. This is not advised since this makes it much harder to
modify translation or add new locale support, but can be useful when developing
or debugging. For example:
{
"label": {
"en-US": "Inline translation dict. Don't do this except for debugging!",
"zh-CN": "..."
}
}
Manage translation files¶
See Localization for ChromeOS Factory Software for detail on how to update or add .po files.
Method reference¶
- cros.factory.test.i18n.translation.DEFAULT_LOCALE = 'en-US'¶
The default locale used in code.
- cros.factory.test.i18n._(_format_string, **kwargs)¶
Wrapper for i18n string processing.
This function acts as
Translation()
when no kwargs is given, and asStringFormat()
when kwargs is given. This function is also a marker for pygettext.
- cros.factory.test.i18n.Translated(obj, translate=True)¶
Ensure that the argument is a translation dict, pass it to
Translation()
orNoTranslation()
if it isn’t.This will also make sure that the return translation dict contains all supported locales. The value of
DEFAULT_LOCALE
would be used to fill in locales not in the input obj regardless of the argument translate.- Parameters
obj – The string to be translated, or the translation dict.
translate – True to pass things that are not translation dict to
Translation()
, False to pass toNoTranslation()
.
- Returns
The translation dict.
- cros.factory.test.i18n.StringFormat(_format_string, **kwargs)¶
Do format string on a translation dict.
- Parameters
_format_string – The format string used, can either be a string (would be passed to
Translation()
), or a translation dict.kwargs – arguments of format string.
Example:
StringFormat('{str1} {str2}', str1='String-1', str2=_('String-2'))
If the text
'{str1} {str2}'
has translation'{str1}-{str2}'
in zh-CN, the text'String-1'
has translation'Text-1'
in zh-CN, the text'String-2'
has translation'Text-2'
in zh-CN, then the returned translation dict would be:{ 'en-US': 'String-1 String-2', 'zh-CN': 'String-1-Text-2' }
- cros.factory.test.i18n.NoTranslation(obj)¶
Get a translation dict that maps the input unmodified for all supported locales.
Used to explicitly set an object as don’t translate when passing to various i18n functions.
- Parameters
obj – The object to be used.
- Returns
The translation dict for all supported locales.
- cros.factory.test.i18n.Translation(text)¶
Get the translation dict in all supported locales of a string.
- Parameters
text – The string to be translated.
- Returns
The translation dict for all supported locales.
- cros.factory.test.i18n.HTMLEscape(text)¶
HTML-escape all entries in a given translation dict.
- Parameters
text – The translation dict to be HTML-escaped.
- Returns
The new translation dict with all values HTML-escaped.
- cros.factory.test.i18n.arg_utils.I18nArg(name, help_msg, default=_DEFAULT_NOT_SET)¶
Define an argument for i18n text.
The argument should either be a string that would be passed to
Translation()
, or a dict looks like:{ 'en-US': 'Default English value', 'zh-CN': 'Chinese Translate' }
The key
'en-US'
is mandatory and would be used for locales that don’t have value specified.- Parameters
name – The name of the argument.
help_msg – The help message of the argument.
default – The default value of the message.
- Returns
The
arg_utils.Arg
object.