-
Notifications
You must be signed in to change notification settings - Fork 14
Integration with Flutter Intl
This sample shows how to integrate with the official Flutter approach.
Click to expand
If you come from the Setup Wiki, delete the following folders and their contents so you can start clean:
- /strings/
- /assets/
- /lib/i18n/
Add the needed dependencies in pubspec.yaml, as stated in the docs:
## pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.17.0
# ...
flutter:
uses-material-design: true
generate: true
Create l10n.yaml
(required by intl generator) in your project's root with the following content:
## l10.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
It will look for the following file lib/l10n/app_en.arb
to auto-generate the Dart code everytime you run flutter pub get
.
Let's use a handy fts feature to get the Strings you currently have in the counter app.
Open your terminal and execute:
fts extract -s -p lib/ -o strings/strings.yaml
extract searches recursively in --path lib/
for any dart files (check fts extract --help
to define other extensions), and tries to capture the Strings it finds, by default creating the keys based on a counter (textN
) and a folder-file structure.
strings/strings.yaml
main:
text1: Flutter Demo
text2: Flutter Demo Home Page
text3: "You have pushed the button this many times:"
text4: "{{counter}}"
text5: Increment
In this case the Counter App is way too simple, just a few lines of code. But in real projects,
extract
can truly help to keep the keys and strings in a single place very fast.
Clean, and rename keys in the master strings file to keep what we will use:
appbarTitle: Flutter Demo Home Page
message: "You have pushed the button {{counter}} times:"
tooltip: Increment
Configure the output from trconfig.yaml
Open trconfig.yaml and comment (or remove) the following lines:
## output_json_template: assets/i18n/*.json
and
## keys_id: TKeys
We will not use the autogenerated Keys nor the JSON files this time.
Intl expects .arb files as defined in l10n.yaml, so add the following lines to trconfig.yaml
output_arb_template: lib/l10n/app_*.arb
# modify the entry_file to use our extracted strings.
entry_file: strings/strings.yaml
With comments removed, trconfig.yaml should look something like this:
output_arb_template: lib/l10n/app_*.arb
entry_file: strings/strings.yaml
param_output_pattern: "{*}"
dart:
output_dir: lib/i18n
translations_id: TData
use_maps: false
locales:
- en
- es
- ja
gsheets:
use_iterative_cache: false
credentials_path: credentials.json
spreadsheet_id: {SHEET_ID} ## Sheet id from Configuration setup page.
worksheet: Sheet1
In the Terminal execute fts run
again. The output might be a little verbose, but shows you the progress while it tries to sync the data, clearing some, now, inexistent keys from the sheet and creating new rows for the data we just defined in strings.yaml
The worksheet might look like this:
And your project should have new files and folders generated:
Open lib/main.dart
and add the missing code from intl article:
import 'package:flutter_localizations/flutter_localizations.dart';
/// auto generated after you run `flutter pub get`
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
And
return MaterialApp(
title: 'Localizations Sample App',
localizationsDelegates: [
AppLocalizations.delegate, // Add this line
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', ''), // English, no country code
Locale('es', ''), // Spanish, no country code
],
home: MyHomePage(), /// add the title directly inside the widget.
);
Inside _MyHomePageState
, let's try to show the localized title first
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
// 'Flutter Demo Home Page',
AppLocalizations.of(context)!.appbarTitle,
),
),
Set an existing locale to MaterialApp
... the default template already generated en, es and ja if you didn't modify it.
return MaterialApp(
title: 'Localizations Sample App',
locale: Locale('ja'),
After a hot-reload, you should see the localization message applied:
Add the missing strings to your widget :
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(loc.appbarTitle), /// here
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(loc.message(_counter)), /// here
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: loc.tooltip, /// here
child: Icon(Icons.add),
),
);
}
... try changing local in MaterialApp
to Locale('es')
Seems like GoogleTranslate didn't translate the text properly, you will see later how to modify the translations.
fts
generates some utility code at lib/i18n/i18n.dart
to simplify part of the integration.
AppLocales
contains the available Locales and LangVo
objects (LangVo
(ValueObjects models) holds some useful properties: english name, native name, emoji flag and locale id and instance) of the current available locales.
For quick tests switching languages, SimpleLangPicker
, is a dropdown menu that automatically fills the items with AppLocales.available
.
In order to change Locale (language), we need to re-build MaterialApp
, you might do this with your preferred State Management solution, to keep it simple, we will use ValueNotifier
.
/// The "master" locale is the one we use in our master template, the first locale in trconfig.yaml
/// in our case, "en"
final currentLocale = ValueNotifier<Locale>(AppLocales.supportedLocales.first);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Locale>(
valueListenable: currentLocale, /// the ValueNotifier
builder: (_, locale, __) => MaterialApp(
title: 'Localizations Sample App',
supportedLocales: AppLocales.supportedLocales, /// a list of predefined Locales
locale: locale,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: MyHomePage(),
),
);
}
}
This way, assigning a new value to currentLocale.value
will rebuild MaterialApp
.
return Scaffold(
appBar: AppBar(
title: Text(loc.appbarTitle),
actions: [
SimpleLangPicker(
onSelected: (locale) => currentLocale.value = locale,
selected: currentLocale.value,
),
],
),
The power of fts is the simplicity to keep a single structured data source. Add a a few more languages to trconfig.yaml.
locales:
- en
- es
- ja
- fa # Persian
- el # Greek
- ru # Russian
- zh # simplified Chinese
open Terminal and run:
fts run
Right away, new columns in your worksheet will hold the new available locales with the automatic translations and everything will be synced (also the app bundles.)
... hot-reload
Based on the official docs, the supported plural forms are:
- =0 : zero
- =1 : one
- =2 : two
- few : few
- many : many
- other : other (required)
To define a plural message, we need a variable that defines quantity. After you define the
key:
, use this pseudo-code structure as reference:keyName: plural:quantityVariable: =1: Singular =other: Default plural form
Modify strings/strings.yaml
to use plurals:
appbarTitle: Flutter Demo Home Page
tooltip: Increment
counterMessage:
plural:count:
=0: "You haven't pushed the button"
=1: "You pushed the button once"
=2: "You pushed the button twice"
other: "You pushed the button {{count}} times"
Sync the changes running fts run
If you check the worksheet, each variant of the plural takes a row.
And the placeholder {{count}}
is replaced by {{0}}
. This is to avoid the translation of the variable name, in some weird cases GoogleTranslate might break the braces, like {0}}
, but fts
will detect the pattern anyway. The actual variables names are saved locally next to your master yaml file, in vars.lock
.
As I speak Spanish, I can spot an inaccurate translation in the es
column.
When you try to edit the cell's text, you will see the translate formula instead:
Although you can type right away and replace the cell's content with the raw text, might be more practical to have the raw translated text:
- Select all cells (⌘+A or Ctrl+A)
- Copy (⌘+C or Ctrl+C)
- Paste "special" (⌘+Shift+V or Ctrl+Shift+V)
Or right click on the selection and find the context menu item:
In this case, the modification is simple: "Empujaste" > "Apretaste". But it requires fts
to get the updated strings from the sheet...
Instead of using the run
command, we can use fetch
, as there's no need to sync the source data into the sheet... only retrieve the values, so overall, it's a faster and light process as long as you didn't change the keys or master text in strings.yaml
.
Run in your terminal:
fts fetch
And the output files (arb in this case) will be updated.
Back to main.dart
, modify the Scaffold's body to use the counterMessage
instead of the default counter Text:
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(loc.counterMessage(_counter)), /// new line, takes the count (int)
],
),
),
... hot-reload
For a reference of supported types and format using arb check this link.
Currently Flutter's Intl code generator doesn't support Gender and Selector modifiers.
If you are on macos, the cli will update the localization for ios and macos projects.