From 67dff5130a77094e23c4c25f14617c05303027d2 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Thu, 14 Sep 2023 16:48:38 -0700 Subject: [PATCH] docs: Make an editing pass on new translation docs Mostly this revises the text to be more fully oriented toward giving instructions on what one needs to know for working on the app, and in particular for adding UI features. Also add the fun and helpful fact that a hot reload is enough to cause the bindings to get updated from the ARB files. And tweak the descriptions on a couple of strings; these label just a single item, not a section. --- README.md | 10 +--- assets/l10n/app_en.arb | 4 +- docs/translation.md | 130 +++++++++++++++++++++++++++++------------ 3 files changed, 97 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index bdbe5b65dc..14950f1488 100644 --- a/README.md +++ b/README.md @@ -145,14 +145,10 @@ The generated files that most frequently need an update are run `flutter pub get && flutter build ios --config-only && flutter build macos --config-only`. -### Translation +### Translations and i18n -We currently have a framework for string translation in place that -incorporates the `flutter_localizations` package and has some -example usages. - -For information on how the dart bindings are generated and how -to add new strings, refer to the [translation docs](docs/translation.md). +When adding new strings in the UI, we set them up to be translated. +For details on how to do this, see the [translation doc](docs/translation.md). ## License diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 45f7fa33fa..f642df803c 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -9,11 +9,11 @@ }, "aboutPageOpenSourceLicenses": "Open-source licenses", "@aboutPageOpenSourceLicenses": { - "description": "Section heading in About Zulip page to navigate to Open-source Licenses page" + "description": "Item title in About Zulip page to navigate to Licenses page" }, "aboutPageTapToView": "Tap to view", "@aboutPageTapToView": { - "description": "Button label in About Zulip page to navigate to Open-source Licenses page" + "description": "Item subtitle in About Zulip page to navigate to Licenses page" }, "chooseAccountPageTitle": "Choose account", "@chooseAccountPageTitle": { diff --git a/docs/translation.md b/docs/translation.md index f11411b8f5..fcded2ef10 100644 --- a/docs/translation.md +++ b/docs/translation.md @@ -1,26 +1,43 @@ # Translations Our goal is for this app to be localized and offered in many -languages, just like zulip-mobile and zulip web. +languages, just like zulip-mobile and Zulip web. + ## Current state -Currently in place is integration with `flutter_localizations` -package, allowing all flutter UI elements to be localized +We have a framework set up that makes it possible for UI strings +to be translated. (This was issue #275.) This means that when +adding new strings to the UI, instead of using a constant string +in English we'll add the string to that framework. +For details, see below. + +At present not all of the codebase has been migrated to use the framework, +so you'll see some existing code that uses constant strings. +Fixing that is issue #277. + +At present we don't have the strings wired up to a platform for +people to contribute translations. That's issue #276. +Until then, we have only a handful of strings actually translated, +just to make it possible to demonstrate the framework +is working correctly. + -Per the discussion in #275 the approach here is to start with -ARB files and have dart autogenerate the bindings. I believe -this is the most straightforward way when connecting with a -translation management system, as they output ARB files that -we consume (this is also the same way web and mobile works -but with .po or .json files, I believe). +## Adding new UI strings -## Adding new strings +### Adding a string to the translation database -Add the appropriate entry in `assets/l10n/app_en.arb` ensuring -you add a corresponding resource attribute describing the -string in context. Example: +To add a new string in the UI, start by +adding an entry in the ARB file `assets/l10n/app_en.arb`. +This includes a name that you choose for the string, +its value in English, +and a "resource attribute" describing the string in context. +The name will become an identifier in our Dart code. +The description will provide context for people contributing translations. +For example, this entry describes a UI string +named `profileButtonSendDirectMessage` +which appears in English as "Send direct message": ``` "profileButtonSendDirectMessage": "Send direct message", "@profileButtonSendDirectMessage": { @@ -28,39 +45,54 @@ string in context. Example: }, ``` -The bindings are automatically generated when you execute -`flutter run` although you can also manually trigger it -using `flutter gen-l10n`. +Then run the app (with `flutter run` or in your IDE), +or perform a hot reload, +to cause the Dart bindings to be updated based on your +changes to the ARB file. +(You can also trigger an update directly, with `flutter gen-l10n`.) -Untranslated strings will be included in a generated -`build/untranslated_messages.json` file. This output -awaits #276. -## Using in code +### Using a translated string in the code -To utilize in our widgets you need to import the generated -bindings: +To use in our widgets, you need to import the generated bindings: ``` import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; ``` -And in your widget code pull the localizations out of the context: +Then in your widget code, pull the localizations object +off of the Flutter build context: ``` Widget build(BuildContext context) { final zulipLocalizations = ZulipLocalizations.of(context); ``` -And finally access one of the generated properties: -`Text(zulipLocalizations.chooseAccountButtonAddAnAccount)`. +Finally, on the localizations object use the getter +that was generated for the new string: +`Text(zulipLocalizations.profileButtonSendDirectMessage)`. + + +### Strings with placeholders + +When a UI string is a constant per language, with no placeholders, +the generated Dart code provides a simple getter, as seen above. + +When the string takes a placeholder, +the generated Dart binding for it will instead be a function, +taking arguments corresponding to the placeholders. -String that take placeholders are generated as functions -that take arguments: `zulipLocalizations.subscribedToNStreams(store.subscriptions.length)` +For example: +`zulipLocalizations.subscribedToNStreams(store.subscriptions.length)`. -## Hack to enforce locale (for testing, etc) -To manually trigger a locale change for testing I've found -it helpful to add the `localeResolutionCallback` in -`app.dart` to enforce a particular locale: +## Hack to enforce locale (for testing, etc.) + +For testing the app's behavior in different locales, +you can use your device's system settings to +change the preferred language. + +Alternatively, you may find it helpful to +pass a `localeResolutionCallback` to the `MaterialApp` in `app.dart` +to enforce a particular locale: ``` return GlobalStoreWidget( @@ -75,16 +107,20 @@ return GlobalStoreWidget( home: const ChooseAccountPage())); ``` -(careful that returning a locale not in `supportedLocales` -will crash, the default behavior ensures a fallback is -always selected) +(When using this hack, returning a locale not in `supportedLocales` will +cause a crash. +The default behavior without `localeResolutionCallback` ensures +a fallback is always selected.) + ## Tests -Widgets that access localization will fail if the root -`MaterialApp` given in the setup isn't also set up with -localizations. Make sure to add the right -`localizationDelegates` and `supportedLocales`: +Widgets that access localizations will fail if +the ambient `MaterialApp` isn't set up for localizations. +For the `MaterialApp` used in the app, we do this in `app.dart`. +In tests, this typically requires a test's setup code to provide +arguments `localizationDelegates` and `supportedLocales`. +For example: ``` await tester.pumpWidget( @@ -95,3 +131,21 @@ localizations. Make sure to add the right supportedLocales: ZulipLocalizations.supportedLocales, home: PerAccountStoreWidget( ``` + + +## Other notes + +Our approach uses the `flutter_localizations` package. +We use the `gen_l10n` way, where we write ARB files +and the tool generates the Dart bindings. + +As discussed in issue #275, the other way around was +also an option. But this way seems most straightforward +when connecting with a translation management system, +as they output ARB files that we consume. +This also parallels how zulip-mobile works with `.json` files +(and Zulip web, and the Zulip server with `.po` files?) + +A file `build/untranslated_messages.json` is emitted +whenever the Dart bindings are generated from the ARB files. +This output awaits #276.