Texts
A text that a user sees belongs to the module that shows it. The module gives its texts to the localization role, in English and in the other languages that it has them in, and its templates read them through the role. A module that only uses the role works in an app without it too, where each text reads in English. Texts and languages explains the role.
The examples come from the module haptics of Add a setting, whose row on the settings screen has a label.
Declare the texts
static const _texts = TextsData([
LocalizedText(
'title',
en: 'Haptic feedback',
translations: {'uk': 'Вібровідгук'},
),
]);
| Part | Rules |
|---|---|
| The name | A lowerCamelCase identifier, such as title or openDetails, with a name of its own among the texts of the module. Names that differ only in case count as one. |
en | The text in English, which is never empty. The app shows it in every language that the text has no translation into, and in an app without the role. |
translations | The text in other languages, by the code of each: two or three lowercase letters without a region, such as uk. English is not among them, and no translation is empty. Leave a language out to show the text in English there. |
A text takes no parameters and has no plural forms, so no text has a { or a }.
An app can be only in a language in which Flutter has the texts of its own widgets, one of LocalizationRole.supportedLanguages. A translation into any other language is left out of the app. Unless --locales chose the languages, smf create warns of it, with the module and the text.
Give the texts to the role, and read them
The module lists the role in uses, gives it the texts as data, and gives its brick a variable for each text:
uses: {preferencesRole, settingsScreenRole, localizationRole},
BrickContribution(
hapticsSettingBundle,
vars: localizationRole.varsOf(id, _texts),
when: const {settingsScreenRole},
),
localizationRole.data(_texts, when: const {settingsScreenRole}),
The when of both contributions is about the row of haptics, as the section below explains. A module whose texts every app needs leaves it out.
varsOf makes the variable text_<name> for each text, with the name in snake_case: title is text_title, and openDetails is text_open_details. A template reads it where a widget shows the text:
title: Text({{{text_title}}}),
The variable renders by the app:
| App | Text({{{text_title}}}) renders as |
|---|---|
| with the localization role | Text(context.l10n.hapticsTitle), and the file gets the import of lib/core/l10n/l10n.dart |
| without it | Text('Haptic feedback') |
The getter of a text starts with the id of the module in lowerCamelCase, followed by the name of the text with a capital: title of haptics is hapticsTitle, and ok of firebase_core would be firebaseCoreOk.
A template reads a text variable by these rules:
- in three braces, and outside every mustache section;
- where a
BuildContext contextbelow the rootMaterialAppis in scope, such as in thebuildof a widget; - not inside a
constexpression, sincecontext.l10n.hapticsTitleis not a constant. A widget that isconstonly in an app without the role gets the keyword from an inverted section next to the variable, as in{{^has_localization}}const {{/has_localization}}Text({{{text_title}}}).
Give the role the same texts as varsOf gets. In an app with the role, smf create reports a variable whose text no module gave the role, since the module that provides the role would have no getter for it.
Texts that only some apps need
The label of haptics is needed only in an app with a settings screen, where its row is. So the brick that reads it and the data of the texts both name that role in when, as the contributions above do. The settings screen role asks for the first: a module that only uses it generates the file of its row only in an app with a settings screen. The second keeps a text that no screen shows out of the app.
A text variable cannot be read inside {{#has_<role>}}. A text that needs another role goes into a brick of its own, contributed with when of that role, as here. See variables that depend on a role.
Whose texts code may read
The code of a module reads only its own texts and those of the modules in its dependsOn, each through its getter. In an app with the role, the contract harness checks it for every text that code reads from context.l10n or from a variable called l10n.
A module brings no delegate for texts of its own and adds no language to the root of the app. Every delegate of the root has to support each language of the app, and only the role knows them.
Texts in the data of another role
The data that a module gives another role can have a text that a user sees too. The label of a destination is one: a feature gives it to the router role in its routes, as Destination(label: ...), and the layout role shows it. Such a text is a LocalizedText of the module like any other:
- A module that lists the localization role gives that role the same text among its
TextsData. The app then reads the text from its texts, in its language. In an app with the role,smf createreports a text of such data that the module did not give the role, or gave it otherwise. - A module that does not list the role gives the text in English alone, which every app shows.
smf createreports a translation there, which no app would show. - Two such texts of one name that differ are an error, since the app reads a text of a module by its name and would show the same text for both.
Write a feature module has the label of a tab as the example. A role whose data has such texts implements DataWithTexts in the data, and its template reads each text with expressionOf, as the next section shows.
Texts of the template of a role
The template of a role gives its texts the same way, when its role lists the localization role in uses or requires. A render hook takes no variable of varsOf, so the template reads each text with expressionOf. The theme role does this for the texts of its row on the settings screen:
RoleOutput render(RoleHookInput<NoDsl> input) => RoleOutput(
vars: {
'mode_key': SmfNames.dartString(ThemeRole.modeKey),
for (final MapEntry(key: variable, value: text) in _texts.entries)
variable: localizationRole.expressionOf(
input,
const RoleTemplateOrigin(themeRole),
text,
),
},
);
The getter of such a text starts with the id of the role: the text title of the theme role is context.l10n.themeTitle. The template reads its own texts, and the texts in the data that the modules give its role, each as a text of the module that gave it.
In tests
The contract harness checks the rules of the texts in the apps with the localization role. So the registry of the tests of a module with texts has a module that provides the role, such as GenL10nModule of smf_gen_l10n, and one that provides the preferences, which the role requires. The harness then builds the apps of the module with the role and without it.
A test of a module reads what the module gave the role from the role, not from the files of the module that provides it:
final input = localizationRole.hookInput(result.hook!);
final texts = localizationRole.textsIn(input);
final languages = localizationRole.localesIn(input);
textsIn returns the texts of the whole app, each with its owner. An app of the tests has the texts of flutter_core too, the two of its fallback start screen in English and in Ukrainian, so its languages include Ukrainian whatever the module under test gives. A test takes the texts of its own module by their owner:
final own = [
for (final text in texts)
if (text.owner == const ModuleOrigin(HapticsModule.id)) text.getter,
];
See Test a module.