Repository navigation
Proposal for a Simplified I18N Process #41437
Description
Update
The proposal that follows led to a new i18n system for Flutter applications. The new system is part of Flutter 1.19beta and will be part of the following stable release.
A user guide for the final system can be found here: https://flutter.dev/go/i18n-user-guide. A web tutorial is forthcoming.
Introduction
This is a proposal to provide a tool that simplifies setting up and maintaining an internationalized Flutter app. It is based on the Dart intl package. The overall goal is to reduce the number of steps required to use that package in an app with modest i18n requirements.
Current Internationalization Process
Currently, internationalized apps which are based on the intl package must follow a procedure similar to the one outlined in the stocks example. Roughly speaking there are 5 steps:
- Add calls to the static
Intl.messageandIntl.pluralmethods where message strings are needed by the app. - Generate a template ".arb" (application resource bundle) file with
intl_translation:extract_to_arb. - Make copies of the template ".arb" file for each supported locale, and add message translations.
- Generate several source files which collectively encode all of the localizations using
intl_translation:generate_from_arb. - Before the app starts, call
initializeMessages()for each supported locale.
Proposal
The proposed approach is to start with handwritten ".arb" files. One of these files will contain the standard per-message metadata required by the intl package.
A new tool will be used to generate a single class that contains one method per message.
Message lookup is based on the inherited locale, using the current BuildContext:
MyLocalizations.of(context).myMessage()The generated class will provide a LocalizationDelegate which is to be included in the app's list of localization delegates. The generated class itself will depend on intl package; the rest of the app doesn't have to.
This approach, based on segregating the app's localizations in a single class, is similar to the stocks example.
Prototype App and Tool
A prototype of the tool is #41432.
The main elements of the app, including the generated code, are as follows.
The English locale ".arb" file, lib/l10n/demo_en.arb defines the messages. The Spanish locale arb file, lib/l10n/demo_es.arb, just defines message translations.
The generated localizations class, DemoLocalizations in lib/1l0n/demo_localizations.dart, provides all of the messages.
The app includes DemoLocalizations.delegate in its localizationDelegates list, and looks up its messages with DemoLocalizations.of(context).
Activity
escamoteur commentedon Sep 27, 2019
Have you seen this package https://github.com/timfreiheit/r_flutter?
Also just a thought wouldn't it even more comfortable if the input would be one cvs file with all languages in it that easy can be edited with any spread sheet application, so easy to give to translators.
Also have you seen https://marketplace.visualstudio.com/items?itemName=esskar.vscode-flutter-i18n-json
To make is really comfortable you should be able to directly create a new word entry from the IDE.
alan-knight commentedon Sep 27, 2019
The target audience for this are people who doing their own translations by directly modifying the data files? I would have thought most people were using some sort of tool or translation service, but it seems not. Given that it seems reasonable enough. It seems important to consider migration issues when someone scales up to using a tool/service. I think they could probably still generate that way, but could this reasonably support multiple classes/multiple ARB files? Is that something people need/use in larger Flutter applications?
Intl doesn't currently handle formatting of parameters, so if you have something that takes a date or a number, you either have to format it before passing it in as a parameter or it will get the default format. So the rest of the app might still need to depend on intl for that.
Not directly relevant to this proposal, but Google Translator Toolkit, which is the primary thing that supported the ARB format is deprecated. We will really need to support at least one other format. There is a pending bug on intl_translation to make it so that anyone can write a reader/writer external to the package. There's also a converter someone wrote in JS, between ARB files and "Hierarchical JSON" format and it could probably handle others.
CSV files with Unicode in them makes me nervous of escaping issues, but there are a lot of formats. As above, being able to handle lots of them seems good, but we may want to recommend a default.
Does this/can this reasonably combine with the ability to use native translations on mobile platforms? So, would people be more comfortable with editing e.g. Android resource files directly?
There's been a bit of work ongoing on being able to read ICU MessageFormat strings at runtime and format them, as Closure does. That would be another alternative, although it's an entirely different (and much less typed) API so would be a significant change. And those would still have to be embedded in a file format.
escamoteur commentedon Sep 27, 2019
@alan-knight what is the problem with CVS and Unicode?
I still think a format that can be read by office applications would make it much easier.
Another point not to forget is how can we support singular / plural versions of words in different languages?
HansMuller commentedon Sep 27, 2019
The tool could definitely support generating multiple classes for different sets of ARB files. That does seems like a nice kind of flexibility. I don't know if developers need that kind of support now, although I'm hoping we'll hear from them here.
Yes, that's correct.
The ARB format isn't essential to this proposal, however it's what we've recommended that Flutter developers use to date. We could support native message catalog formats, per #37031, as well.
It would be nice to support a platform-independent and widely accepted standard. Assuming that the right API to expose to an app was still a set of methods that returned strings, supporting ICU (or ICU too) sounds like a win.
Tomucha commentedon Oct 2, 2019
Hi! I'm all for handwritten message files and code generation. The compile-time check of the message existence is great. But don't forget that occasionally you need to decide the message at runtime, maybe like this:
var vehicleTypeMessageKey = "VehicleType.${data['type']'}";... so having just generated methods is not enough.
You will find a few more ideas like this in my internationalization package :-)
https://pub.dev/packages/i69n
ashour commentedon Oct 2, 2019
Hello, I hope you're all well. Thanks for your work on Flutter btw. It's an awesome piece of tech and I wish I had Flutter when I was co-founding a mobile app startup years ago. I was recently emailed by @HansMuller, who asked me to chime in. I research and write about i18n. I wrote an article on Flutter + Intl last year (I think that's how Hans found me).
I think this is a good proposal because the current Intl workflow is a bit clunky for smaller projects. This is especially true for projects that don't have automation and sophisticated tooling around i18n. Given this leaner alternative, I think two main pain points from a developer perspective warrant a look:
RE reducing manual script running, I've always wondered while using Flutter + Intl if there could be a watcher that generated and updated message files on the fly while developing. I've actually never seen this in any i18n library and I've always wanted it. I don't know how feasible this is given available Dart code analysis tools VS what would need to be written from scratch for such a watcher.
As for file formats, there are a few I think worth considering (not exclusively, of course, just ones that come to my mind):
ICU and PO are widely used. PO has good adoption among i18n tools and platforms for developers & translators. For a smaller team, however, I think the overhead of generating and compiling ICU or PO is more of a hassle than it's worth. The other options are much more lean and to-the-point for smaller projects.
CSV is attractive for sharing with translators, although I can see it being a pain for developers. CSV headers can be far removed from their respective values, which can make managing messages tricky. Think of a plural message in Arabic with the language's 6 plural forms. How could a developer tell them apart? You could add an identifier to each message component, but that would take away from translators' experience.
JSON would be nice for us coders, but it may be a bit of a hassle for translators.
YAML could be a nice in-betweener for both programmers and translators. YAML has a nice, intuitive syntax with significant white space that I think would be picked up easily by translators. It also allows for various types and hierarchies, which make it flexible enough accommodate plural forms and more complex messages.
It could be worthwhile to pull in some translators that work in software and get their thoughts. They may well have their own bread-and-butter tools that, say, work well with JSON and have wide adoption. In that case JSON would be a very attractive option since we'd really only be solving the developers' problem, not the translators'.
These are my two cents so far. I hope they help a bit.
HansMuller commentedon Oct 2, 2019
@Tomucha - Thanks for the feedback. I think we've taken a similar approach to simplifying building i69n applications :-).
I agree that this sort of thing isn't necessary to solve with generated methods. I would think the app would have to include a method that switched on
vehicleTypeMessageKeyand returned a localized string.Tomucha commentedon Oct 2, 2019
At this point i69n generates the exact switch statement you are suggesting ;-)
szotp commentedon Oct 2, 2019
@ashour what's wrong with .arb files? Flutter already uses them, and they are esentially jsons.
EDIT: Also, the watcher you mentioned kinda exists already. Packages using code generation ( https://github.com/flutter/flutter/wiki/Code-generation-in-Flutter ) already auto update when app is running. The only problem is that analyzer does not seem to pick it up immediately and displays fake errors.
@HansMuller I agree that current state is a mess. Did you consider doing
flutter generatebuilder package? I implemented this inr_flutterand it worked decently well (although IDE & analyzer are not really prepared to handle it). Such package could be eventually embraced by flutter team.HansMuller commentedon Oct 2, 2019
@ashour - Your article on internationalizing Flutter apps is an excellent introduction to this topic!
That's an excellent point. We can definitely do this, even in the context-of hot-reload, in roughly the same way that Flutter handles asset changes.
Currently the "ARB" format, which is used by the Dart intl package and the proposed tool, is mostly just a JSON container for a collection of ICU messages. Additional support (beyond what's in the prototype) for dates, currencies, numbers, gender would move us from the realm of "modest" i18n Flutter apps, to more ambitious ones. I think that would be a good direction to take in the long run.
Support for other message catalog formats came up earlier. We could support other formats with additional code generators or perhaps with a scheme that converts other formats to a common one. XLIFF is another popular alternative.
Thanks for your feedback!
HansMuller commentedon Oct 2, 2019
@szotp - thanks for your feedback.
I'll take a look at r_flutter.
escamoteur commentedon Oct 2, 2019
Looking back when I did.Net programming there was a powerful free Application to manage the resource files including text resources in different languages. That could also in and export different formats.
So the used format might not be so important for users if someone writes a flutter desktop app for this.
So maybe
alan-knight commentedon Oct 2, 2019
Dart has a lot of things that rely on code generation and mechanisms for automatically re-generating on changes, e.g. build_runner. So I would expect automating that to be fairly easy. But I don't think anything is integrated with flutter right now, so I guess you would have to manually run build_runner on the side and have it watch the files.
40 remaining items
kuhnroyal commentedon Aug 21, 2020
@HansMuller @shihaohong Is this now the promoted way for i10n with Flutter 1.20.0?
A couple questions, maybe someone can answer some.
pub getinflutter/dev/toolsfor the generator to work - Will this automatically be installed in the future? Since the whole process starts by itself when I add thei10n.yamlfile, I would expect it to be ready to use at that point.shihaohong commentedon Aug 24, 2020
@kuhnroyal
I'm currently looking into fixing this. This is a result of the gen_l10n tool itself being a package, which is why it requires a
pub getcall since its dependencies change between flutter versions. I'm looking into seeing if I can pre-compile the dependencies with the tool itself so that it does not require re-caching and updating its dependencies.If you're going the synthetic package route (which we're trying to make the default way of doing things through #62395), then the goal is that generated Dart files will not need to be committed to VCS. If you'd still prefer to check in the generated code into VCS, you can turn off the synthetic package option with the ways mentioned in the PR depending on whether you're using an l10n.yaml file or running the tool directly via the command line.
kuhnroyal commentedon Aug 24, 2020
@shihaohong Thanks for the clarification and your work on this!
I would love to not commit the generated files but in my initial tries I ran into problems running
flutter testin CI. The files are not generated before running tests, that would currently leave me with the option to run the tool directly beforehand or committing the files.Are the plans to make this tool available as generator so it can be tied into a
build_runnercall?shihaohong commentedon Aug 24, 2020
@kuhnroyal Actually, the current issue I'm trying to resolve is getting
flutter analyzeto figure out this situation. What's happening is thatflutter analyzefails sometimes because the l10n code had not been generated beforehand. So, I'm trying to getflutter analyzeto runflutter pub getas a step (which I'm trying to have call the synthetic package generation code) so that it does not fail for this reason.I had not considered that if would fail with
flutter test. Could you share more about the kind of problems you're running into? Also, what doesbuild_runnerdo with code generation that you would like to see? (I'm not as familiar with usingbuild_runner).kuhnroyal commentedon Aug 24, 2020
@shihaohong From my understanding with the synthetic route (i10n.yaml) I would not commit the files to VCS. When I checkout a project I am usually able to run
flutter pub getandflutter testwhich would compile the code and run the tests. But now the localization files are missing as they are only being generated duringflutter run/buildso the tests don't compile.The question about
build_runneris related to my test problem. Many projects usebuild_runnergenerators to generate code, mostly*.g.dartfiles for all kinds of libraries, built_value, moor, freezed and lots of other i18n libraries. In most cases the*.g.dartfiles are also not committed so that a dedicated build step is required before running tests or building the project (flutter pub run build_runner build) and many CI pipelines in a lot of projects already use this. There is already an established config file formatbuild.yamlvia https://pub.dev/packages/build_config. I was just wondering if there are plans to provide a generator for build_runner.shihaohong commentedon Aug 24, 2020
@kuhnroyal Great, thanks for the feedback/explanation! I'll take a look into how we can address that in the next few days. I might create a separate issue to track it as well since this one is already closed and will loop you in once I do that.
shihaohong commentedon Aug 30, 2020
@kuhnroyal My synthetic package PR (#62395) incorporates synthetic package generation into
flutter testandflutter analyzeinvocations whenflutter pub getis required, so that should address the issue you mentioned with running tests and compiling code.As for providing a generator for
build_runner, we're currently not planning on incorporating synthetic package generation intobuild_runner, but this shouldn't be a problem as long as there isn't a problem with the synthetic package generation code. Please feel free to file a new issue highlighting any concerns or problems you face with thegen_l10ntool, I'd be happy to look into and try to address them.Sominemo commentedon Sep 7, 2020
I noticed the
output-dirparameter from the docs doesn't exist in codeshihaohong commentedon Sep 8, 2020
@Sominemo Thanks for the heads up! I need to make some updates to the user guide, some of the instructions have changed with some updates to
gen_l10n.Edit: I made the updates to the user guide.
output-dirdefinitely exists, but it's only relevant if you're not using the synthetic package strategy that is on by default. Meaning, the generated code will always go into{flutter_project}/.dart_tool/flutter_gen/gen_l10nby default. If you turn off synthetic packages (synthetic-package: false), it will generate the localizations code inoutput-dir.kuhnroyal commentedon Sep 18, 2020
@shihaohong Did this make it into the new beta?
shihaohong commentedon Sep 22, 2020
Yes, it looks like the synthetic packages commit made it into beta.
kuhnroyal commentedon Sep 30, 2020
@shihaohong The synthetic package seems to break
build_runneras it finds the.dart_tool/flutter_genfolder and expects apubspec.yamlthere.Other things I found:
synthetic-package: falseto work, no code is generated.AppLocalizationsclass can not be found/imported automatically in IntelliJ. Have to write it by hand to make it work.shihaohong commentedon Sep 30, 2020
@kuhnroyal The first issue you mentioned was also mentioned/filed in dart-lang/build#2835.
For the next two, could you create separate issues for them and tag me? It's easier for us to keep track of new issues with the tool that way since these problems can get lost in this issue's comment thread.
github-actions commentedon Aug 12, 2021
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.