Skip to content

Integration with GetX

Roi Peker edited this page Aug 12, 2021 · 4 revisions

This sample shows how to integrate fts with GetX.

Use the following template as starting point.

It uses GetMaterialApp and a local HomePageController to control the counter.

Table of contents:

Click to expand

step 1

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:

dependencies:
  flutter:
    sdk: flutter
  get:

step 2

Let's take the Strings from the template app.

Open your terminal and execute:

fts extract -s -p lib/ -o strings/strings.yaml
show sample

fts extract output.

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: "{{value}}"
  text2: A beautiful counter
  text3: "You have pushed the button this many times:"
  text4: Increment

The Counter App template is very basic, 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: A beautiful counter
message: "You have pushed the button this many times:"
tooltip: Increment

step 3

Configure the output from trconfig.yaml

Open trconfig.yaml and comment (or remove) the following line:

#output_arb_template: lib/l10n/intl_*

We will not use .arb files this time.

Also make these changes:

entry_file: strings/strings.yaml

dart:
## let's make use of hardcoded generated Maps for messages.
  use_maps: true.

With comments removed, trconfig.yaml should look like this:

output_json_template: assets/i18n/*.json
entry_file: strings/strings.yaml
param_output_pattern: "{*}"
dart:
  output_dir: lib/i18n
  keys_id: TKeys
  translations_id: TData
  use_maps: true
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

step 4

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 should look like this:

new data in google sheet

And your project should have new files and folders generated:

getx project view

step 5

Open lib/main.dart and modify GetMaterialApp to support locales:

import 'i18n/i18n.dart';
/// ...
return GetMaterialApp(
  debugShowCheckedModeBanner: false,
  localeResolutionCallback: (_, __) => const Locale('en'),
  locale: Locale('es'), /// new
  supportedLocales: AppLocales.supportedLocales, /// new
  translationsKeys: TData.byText, /// new
  home: HomePageView(),
);

Warning: In Flutter >= 2.2.3, setting localeResolutionCallback avoids the exception "No MaterialLocalizations found." when not using flutter_localizations dependency.

Set an existing locale to GetMaterialApp... the default template already generated en, es and ja if you didn't modify it.

Getx localization works with a simple key/value Map... that's why we defined use_maps: true in trconfig.yaml. Yet, with this simple approach, we will be able to load external .json files and use the same code.

By using TData.byText we will associate the master text to the yaml keys, instead of consuming the keys. So just append .tr to your Strings. Let's change the AppBar's title:

appBar: AppBar(
  title: Text('A beautiful counter'.tr),
),

After hot-restart you should see: getx localized appbar title in es

Is really easy to get a String translated in this way; yet, we should agree that relying on existing Strings in the app as keys is dangerous, and doesn't scale well. Just know that you have the option to quickly localize your Strings this way.

To be safe, we'll use the keys instead.

Change in GetMaterialApp:

translationsKeys: TData.byKeys,

Using the keys defined in trconfig.yaml, in step 2, we access the Map associated with the current locale.

Let's change the AppBar's title again:

appBar: AppBar(
  // title: Text('A beautiful counter'.tr),
  title: Text('appbarTitle'.tr), // new
),

You should see exactly the same text. Using keys like this allow us to change the master text independently from the code, and scale without issues. But in bigger apps, when you have hundreds of localized strings, becomes tedious very soon to look which key is the correct one for specific UI element.

Also, hardcoded Strings are error-prone, let's say we make a typo and write appbarTitl or appbartitle ... the key doesn't exists in the translation Map, and even if the missing key error will be visible on screen while you test your app; if the app is big enough, with several nested Routes, it will become risky to make a mistake you will not notice.

That's why we have keys_id: TKeys in trconfig.yaml. It resolves the key access in a Type-safe way, navigating through the objects, replicating the master text structure in strings.yaml

appBar: AppBar(
  // title: Text('A beautiful counter'.tr),
  // title: Text('appbarTitle'.tr), 
  title: Text(TKeys.appbarTitle.tr), // new
),

Now, we have code hints in the IDE for the keys (remember it just resolves the actual key String, so you have to add the .tr)

using TKeys in getx

step 6

Maybe you notice we need to add the variable counter in our Strings

GetX Placeholders

In getx you have 2 ways to add the placeholders:

  • using positional arguments List<String>
  • or parameters Map<String,String>.

The translation parameters in GetX are interpreted as @variable, so we need to modify trconfig.yaml to generate placeholders in that fashion. That's the purpose of param_output_pattern:

## param_output_pattern: "{*}"
param_output_pattern: "@*" ## new

As we don't change our master data structure in strings.yaml, you can run fts fetch.

Let's change the code to take parameters:

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Obx(
        () => Text(
          TKeys.message.trParams({'counter': controller.counterText}),
        ),
      ),
    ],
  ),
),

... hot-restart:

getx trParams

step 7

fts code utilities:

As we noticed, 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.

Make language change

In order to change Locale (language), we need to re-build GetMaterialApp, with Getx is pretty simple, just call Get.updateLocale() and the Widgets will rebuild.

Let's add the language selector:

appBar: AppBar(
  title: Text(TKeys.appbarTitle.tr),
  actions: [
    SimpleLangPicker(
      onSelected: Get.updateLocale,
      selected: Get.locale,
    ),
  ],
),

SimpleLangPicker

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-restart

new locales on hot reload

Modifying translations

If you speak Spanish, you can spot the inaccurate translations in the es column in the worksheet.

When you try to edit the cell's text, you will see the translate formula instead: cell formula

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:

  1. Select all cells (⌘+A or Ctrl+A)
  2. Copy (⌘+C or Ctrl+C)
  3. Paste "special" (⌘+Shift+V or Ctrl+Shift+V)

Or right click on the selection and find the context menu item: Paste Special

Every row requires a modification:

  • "Un hermoso mostrador" > "Un hermoso contador"
  • "Has presionado el botón {{0}} Times:" > "Has presionado el botón {{0}} veces:"
  • "Incremento:" > "Incrementar"

fts needs 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 lighter process, as long as you didn't change the keys or master text in strings.yaml. In that case your data will end up out of sync.

Run in your terminal:

fts fetch

And the output files (dart files in this case) will be updated:

update text with fts fetch


Later we will show how to use the generated json files to load your localized texts at runtime.