i18next 0.0.1+7

  • Readme
  • Changelog
  • Example
  • Installing
  • 65

i18next #

This is an adaptation of i18next standard in dart. This package is still a work in progress. Mind that this is still a pre-1.0.0 so breaking changes may occur frequently.

  • [x] Support for variables
  • [x] Support for namespaces
  • [x] Support for context
  • [x] Support for simple plural forms (one or plural)
  • [ ] Support for multiple plural forms (one, few, many, plural, ...)
  • [x] Plural and context fallbacks
  • [ ] Locale and namespace fallbacks
  • [x] Get string or object tree
  • [x] Support for nesting
  • [ ] Sprintf support
  • [x] Flutter's LocalizationsDelegate support
  • [x] Asset bundle localizations data source (retrieves from pubspec.yaml). See the example for more details.
  • [ ] Resource caching :wip:
  • [ ] Retrieve resource files from server :wip:
  • [ ] Custom post processing

Usage #

Simply declare the package in your pubspec.yaml

dependencies:
  i18next: ^0.0.1

To use it with flutter's LcoalizationsDelegate you first create I18NextLocalizationDelegate and register it in your WidgetsApp (MaterialApp or CupertinoApp).

I18NextLocalizationDelegate(
  locales: widget.locales,
  // this data source is from where the delegate will retrieve the localizations from (namespaces Map)
  dataSource: ...,
  // optional extra options can be added here
  options: I18NextOptions(...),
),

Then to access and use it, simply call

Widget build(BuildContext context) {
  // It finds the i18next instance on the widgets tree via `Localizations.of`
  I18Next.of(context).t(...);
  ...
}

But if you want to handle it yourself, then simply instantiate it:

I18Next(
  locale,
  // This store is from where i18next will attempt to retrieve the localizations from.
  resourceStore: ...,
  // Optional extra options can be added here
  options: I18NextOptions(...)
);

Syntax #

For the simple and straightforward usages:

{
  "key": "Hello World!",
  "nested": {
    "key": "My nested key"
  }
}
i18next.t('key'); // 'Hello World!'
i18next.t('nested.key'); // 'My nested key'

// unmapped keys usually return themselves (when graceful fallback fails)
i18next.t('unspecifiedKey'); // 'unspecifiedKey'
{
  "myKey": "Hello {{name}}!"
}
i18next.t('key', arguments: {'name': 'World'}); // 'Hello World!'
{
  "nesting1": "1 $t(nesting2)",
  "nesting2": "2 $t(nesting3)",
  "nesting3": "3"
}
i18next.t('nesting1'); // "1 2 3"
{
  "key": "item",
  "key_plural": "items",
  "keyWithCount": "{{count}} item",
  "keyWithCount_plural": "{{count}} items"
}
i18next.t('key', count: 0); // 'items'
i18next.t('key', count: 1); // 'item'
i18next.t('key', count: 5); // 'items'
i18next.t('keyWithCount', count: 0); // '0 items'
i18next.t('keyWithCount', count: 1); // '1 item'
i18next.t('keyWithCount', count: 5); // '5 items'

There are also ways of dealing with locales with multiple plural: zero, one, few, many, others (key identifier) (Unsupported)

  • Contexts like gender, are marked via underscores
{
    "genderMessage": "They", 
    "genderMessage_male": "Him",
    "genderMessage_female": "Her"
}
i18next.t('genderMessage'); // 'They'
i18next.t('genderMessage', context: 'male'); // 'Him'
i18next.t('genderMessage', context: 'female'); // 'Her'

And can be used with plurals

{
  "friend": "A friend",
  "friend_plural": "{{count}} friends",
  "friend_male": "A boyfriend",
  "friend_female": "A girlfriend",
  "friend_male_plural": "{{count}} boyfriends",
  "friend_female_plural": "{{count}} girlfriends"
}
i18next.t('friend'); // 'A friend'
i18next.t('friend', count: 1); // 'A friend'
i18next.t('friend', count: 100); // '100 friends'

i18next.t('friend', context: 'male', count: 1); // 'A boyfriend'
i18next.t('friend', context: 'female', count: 1); // 'A girlfriend'
i18next.t('friend', context: 'male', count: 100); // '100 boyfriends'
i18next.t('friend', context: 'female', count: 100); // '100 girlfriends'
{
  "key1": "The current date is {{now, MM/DD/YYYY}}",
  "key2": "{{text, uppercase}} just uppercased"
}
i18next.t('key1', arguments: { 'now': DateTime.now() }); // 'The current date is 01/01/2020'
i18next.t('key2', arguments: { 'text': 'my text' }); // 'MY TEXT just uppercased'

There are other usages and possibilities as well, this is just an example of what is defined by this format.

  • Namespaces: A namespace can be thought of as logical groupings of different sets of translations. In a given namespace you could have a set of languages, each with their own set of keys. They can also be understood as separate files. For example:

    • common.json: Things that are reused everywhere, eg. Button labels 'save', 'cancel'
    • validation.json: All validation texts
    • glossary.json: Words we want to be reused consistently inside texts
// common.json
{
  "myKey": "This key is in common"
}

// feature.json
{
  "myKey": "This key is in my feature"
}
i18next.t('common:myKey'); // 'This key is in common'
i18next.t('feature:myKey'); // 'This key is in my feature'
  • Context/plural fallback mechanism:
{
  "friend": "A friend",
  "friend_female": "A girlfriend" 
}
i18next.t('friend'); // 'A friend'

i18next.t('friend', count: 1); // 'A friend'
// It fallbacks to `friend` since `friend_plural` is not present
i18next.t('friend', count: 2); // 'A friend' 

i18next.t('friend', context: 'female'); // 'A girlfriend'
// It fallbacks to `friend` since `friend_male` is not present
i18next.t('friend', context: 'male'); // 'A friend' 

There is a way to also set the default namespace or a order of namespaces so a key knows where to start looking for the translation.

[0.0.1+7]

  • Change the namespaces type from Map<String, Map<String, Object>> -> Map<String, Object>
  • Adds I18Next.of(BuildContext) from Localizations
  • Adds I18NextLocalizationDelegate
  • Adds convenience methods to ResourceStore for adding, removing, and verifiying locales and namespaces
  • Adds asset bundle data source and the LocalizationDataSource interface
  • Changes links to nubank/i18next
  • Adds example app

[0.0.1+6]

  • Migrated repository to williamhjcho/i18next
  • Reduce description size

[0.0.1+5]

  • Adds plural separator in I18NextOptions
  • Adds key separator in I18NextOptions
  • Adds and replaces LocalizationDataSource for ResourceStore
  • Makes I18Next.t's parameters supersede the options parameter
  • Removes Map extension from I18NextOptions
  • Makes I18NextOptions Diagnosticable
  • Improves and adds more cases on Interpolator

[0.0.1+4]

  • Renames arguments to variables
  • Replaces InterpolationOptions for I18NextOptions
  • Updates I18Next inner workings to more contextualized methods.
  • Escapes interpolation strings in options for RegExp
  • Adds base nesting mechanism
  • Isolates Translator, PluralResolver, and Interpolator into separate classes
  • Makes I18NextOptions's properties optional and allows individual overrides
  • Makes I18NextOption conform to Map<String, Object>
  • Reduces API surface by merging most of the optional properties into I18NextOptions itself
  • Moves pattern builders from options to the classes themselves
  • Keeps property variables in I18NextOptions while keeping Map extension.
  • Adds/merges locale property in I18NextOptions

[0.0.1+3]

  • Adds InterpolationOption
  • Allows locale and interpolation options override on t
  • Adds a little more documentation

Internal:

  • Splits data fetching and translation into separate methods

[0.0.1] - TODO: Add release date.

  • TODO: Describe initial release.

example/lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:i18next/i18next.dart';
import 'package:intl/intl.dart';

import 'localizations.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  final locales = const [
    Locale('en', 'US'),
    Locale('pt', 'BR'),
    // TODO: add multi plural language(s)
  ];

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale locale;

  @override
  void initState() {
    super.initState();

    locale = widget.locales.first;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'I18nu Demo',
      theme: ThemeData(
        dividerTheme: DividerThemeData(
          color: Colors.black45,
          space: 32.0,
        ),
      ),
      localizationsDelegates: [
        ...GlobalMaterialLocalizations.delegates,
        I18NextLocalizationDelegate(
          locales: widget.locales,
          dataSource: AssetBundleLocalizationDataSource(
            // This is the path for the files declared in pubspec which should
            // contain all of your localizations
            bundlePath: 'localizations',
          ),
          // extra formatting options can be added here
          options: I18NextOptions(formatter: formatter),
        ),
      ],
      home: MyHomePage(
        supportedLocales: widget.locales,
        onUpdateLocale: updateLocale,
      ),
      locale: locale,
      supportedLocales: widget.locales,
    );
  }

  void updateLocale(Locale newLocale) {
    setState(() {
      locale = newLocale;
    });
  }

  static String formatter(Object value, String format, Locale locale) {
    switch (format) {
      case 'uppercase':
        return value.toString().toUpperCase();
      case 'lowercase':
        return value.toString().toLowerCase();
      default:
        if (value is DateTime) {
          return DateFormat(format, locale?.toString()).format(value);
        }
    }
    return value.toString();
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    Key key,
    @required this.supportedLocales,
    @required this.onUpdateLocale,
  }) : super(key: key);

  final List<Locale> supportedLocales;
  final ValueChanged<Locale> onUpdateLocale;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _gender = '';

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final homepageL10n = HomePageL10n.of(context);
    final counterL10n = CounterL10n.of(context);

    return Scaffold(
      appBar: AppBar(title: Text(homepageL10n.title)),
      body: SingleChildScrollView(
        padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            CupertinoSegmentedControl<Locale>(
              children: Map.fromIterable(
                widget.supportedLocales,
                key: (dynamic loc) => loc,
                value: (dynamic locale) => Text(locale.toString()),
              ),
              groupValue: Localizations.localeOf(context),
              onValueChanged: widget.onUpdateLocale,
            ),
            const Divider(),
            Text(
              homepageL10n.hello(name: 'Name', world: 'Flutter'),
              style: theme.textTheme.title,
            ),
            Text(
              homepageL10n.today(DateTime.now()),
              style: theme.textTheme.subtitle,
            ),
            CupertinoSegmentedControl<String>(
              padding: const EdgeInsets.symmetric(vertical: 8),
              children: const {
                'male': Text('MALE'),
                'female': Text('FEMALE'),
                '': Text('OTHER'),
              },
              groupValue: _gender,
              onValueChanged: updateGender,
            ),
            Text(homepageL10n.gendered(_gender)),
            const Divider(),
            Text(
              counterL10n.clicked(_counter),
              style: theme.textTheme.display1,
            ),
            FlatButton(
              child: Text(counterL10n.resetCounter),
              onPressed: resetCounter,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        tooltip: counterL10n.clickMe,
        child: Icon(Icons.add),
      ),
    );
  }

  void incrementCounter() => setState(() => _counter++);

  void resetCounter() => setState(() => _counter = 0);

  void updateGender(String gender) => setState(() => _gender = gender);
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  i18next: ^0.0.1+7

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:i18next/i18next.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
33
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
65
Learn more about scoring.

We analyzed this package on Nov 15, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.0
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.6

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.3.0 <3.0.0
flutter 0.0.0
path ^1.6.0 1.6.4
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.7 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test
mockito ^4.0.0