Last updated:
0 purchases
i69n
Simple internationalization (i18n) package for Dart and Flutter.
Supports:
Flutter, hot reload included
AngularDart
deferred loading of translations
social distancing
null safety
Overview #
Turn this YAML file:
lib/exampleMessages.i69n.yaml
button:
save: Save
load: Load
users:
welcome(String name): "Hello $name!"
logout: Logout
copied to clipboard
Into these generated Dart classes:
class ExampleMessages {
const ExampleMessages();
ButtonExampleMessages get button => ButtonExampleMessages(this);
UsersExampleMessages get users => UsersExampleMessages(this);
}
class ButtonExampleMessages {
final ExampleMessages _parent;
const ButtonExampleMessages(this._parent);
String get save => "Save";
String get load => "Load";
}
class UsersExampleMessages {
final ExampleMessages _parent;
const UsersExampleMessages(this._parent);
String get logout => "Logout";
String welcome(String name) => "Hello $name!";
}
copied to clipboard
... and use them in your code - plain and simple.
ExampleMessages m = ExampleMessages();
print(m.users.welcome('World'));
// outputs: Hello World!
copied to clipboard
Package is an extension (custom builder) for build_runner
(Dart standard for source generation) and it can be used with Flutter, AngularDart
or any other type of Dart project.
i69n: 51 points simpler than your standard i18n! #
The official Dart/Flutter approach to i18n seems to be ... complicated and kind of ... heavyweight.
I would like my messages to be checked during compile time. Is that message really there?
Key to the localized message shouldn't be just some arbitrary String, it should be a getter method!
And if the message takes some parameters, it should be a method which take those parameters!
How to use with Flutter #
Create YAML file with your messages, for example:
lib/messages/foo.i69n.yaml
generic:
done: Done
ok: OK
invoice:
create: Create an invoice
delete: Delete this invoice
copied to clipboard
Add translations for different languages:
lib/messages/foo_cs.i69n.yaml (_cs = Czech translation)
generic:
done: Hotovo
# ok is the same and Foo_cs extends Foo.
invoice:
create: Vytvořit fakturu
delete: Smazat fakturu
copied to clipboard
Add build_runner as a dev_dependency and i69n as a dependency to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
i69n: any
...
dev_dependencies:
build_runner: any
flutter_test:
sdk: flutter
copied to clipboard
Open a terminal and in the root of your Flutter project run:
flutter packages pub run build_runner watch --delete-conflicting-outputs
copied to clipboard
... and keep it running. Your message classes will appear next to YAML files and will be
rebuilt automatically each time you change the source YAML.
For one-time (re)build of your messages run:
flutter packages pub run build_runner build --delete-conflicting-outputs
copied to clipboard
Import generated messages and use them:
import 'packages:my_app/messages/foo.i69n.dart'
...
Foo m = Foo();
return Text(m.bar);
...
copied to clipboard
... or ...
import 'packages:my_app/messages/foo_cs.i69n.dart'
Foo m = Foo_cs(); // Notice: Foo_cs extends Foo
return Text(m.bar);
copied to clipboard
How to use with AngularDart #
You are using webdev tool already, so you just need to add i69n
as a dependency and that's all.
Parameters and pluralization #
The implementation is VERY straightforward, which allows you to do all sorts of crazy stuff:
invoice:
create: Create invoice
delete: Delete invoice
help: "Use this function
to generate new invoices and stuff.
Awesome!"
count(int cnt): "You have created $cnt ${_plural(cnt, one:'invoice', many:'invoices')}."
apples:
_apples(int cnt): "${_plural(cnt, one:'apple', many:'apples')}"
count(int cnt): "You have eaten $cnt ${_apples(cnt)}."
copied to clipboard
Now see the generated classes:
class ExampleMessages {
const ExampleMessages();
InvoiceExampleMessages get invoice => InvoiceExampleMessages(this);
ApplesExampleMessages get apples => ApplesExampleMessages(this);
}
class InvoiceExampleMessages {
final ExampleMessages _parent;
const InvoiceExampleMessages(this._parent);
String get create => "Create invoice";
String get help => "Use this function to generate new invoices and stuff. Awesome!";
String get delete => "Delete invoice";
String count(int cnt) => "You have created $cnt ${_plural(cnt, one:'invoice', many:'invoices')}.";
}
class ApplesExampleMessages {
final ExampleMessages _parent;
const ApplesExampleMessages(this._parent);
String _apples(int cnt) => "${_plural(cnt, one:'apple', many:'apples')}";
String count(int cnt) => "You have eaten $cnt ${_apples(cnt)}.";
}
copied to clipboard
See how you can reuse the pluralization of _apples(int cnt)? (!!!)
There are three functions you can use in your message:
String _plural(int count, {String zero, String one, String two, String few, String many, String other})
String _cardinal(int count, {String zero, String one, String two, String few, String many, String other})
String _ordinal(int count, {String zero, String one, String two, String few, String many, String other})
copied to clipboard
_plural and _cardinal do the same. I just felt that _plural
sounds a little bit less scary and in most cases that's the one you need.
We need only two forms of the word "apple" in English. "Apple" (one) and "apples" (many).
But in Czech, we need three:
apples:
_apples(int cnt): "${_plural(cnt, one:'jablko', few: 'jablka', many:'jablek')}"
copied to clipboard
See also:
http://cldr.unicode.org/index/cldr-spec/plural-rules
https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
How to use generated classes #
How to decide what translation to use (ExampleMessages_cs?, ExampleMessages_hu?) is up to you.
The package simply generates message classes, that's all.
import 'exampleMessages.i69n.dart';
import 'exampleMessages_cs.i69n.dart' deferred as cs;
void main() async {
ExampleMessages m = ExampleMessages();
print(m.apples.count(1));
print(m.apples.count(2));
print(m.apples.count(5));
await cs.loadLibrary();
m = cs.ExampleMessages_cs(); // see? ExampleMessages_cs extends ExampleMessages
print(m.apples.count(1));
print(m.apples.count(2));
print(m.apples.count(5));
}
copied to clipboard
Where and how to store instances of these message classes -
again, up to you. I would consider ScopedModel for Flutter and registering
messages instance into dependency injection in AngularDart.
But in this case a singleton would be acceptable also.
Customization and speacial features #
Support for regions #
You might need to support multiple regions for one language. Use common locale names as file suffixes:
messages.i69n.yaml
messages.en_US.i69n.yaml
messages.en_GB.i69n.yaml
copied to clipboard
Dynamic access using String keys #
It's still useful to access your messages using the String keys in some cases. For example when the key
of the message is composed dynamically at runtime, maybe like this:
var vehicleTypeMessageKey = "VehicleType.${data['type']'}";
copied to clipboard
You can access your messages like this:
print('Static: ${m.generic.ok}');
print('Dynamic: ${m.generic['ok']}');
print('Or even: ${m['generic.ok']}');
copied to clipboard
In case the key doesn't exist, an exception is thrown:
...
throw Exception('Message $key doesn\'t exist in $this');
copied to clipboard
You can disable this behaviour by adding flag 'nothrow':
_i69n: nothrow
generic:
done: Done
ok: OK
copied to clipboard
This flag is inherited to the lower levels of messages structure.
In some rare cases you might want to 'disable' this map generation (maybe to enable better tree-shaking of unused messages?).
In such case use a simple flag 'nomap':
generic:
_i69n: nomap
done: Done
ok: OK
copied to clipboard
No message in 'generic' message group will be accessible through the [] operator. Scope of the flag is very narrow - one message object
in one file. Flag is not inherited into lower levels of messages and in
most cases you want to repeat it in all translations files to make an impact.
Escaping special characters #
i69n uses double quotes in generated source files. Unless you need to use double quotes in your string,
you should be fine.
message: Let's go!
...
String get message => "Let's go!";
copied to clipboard
Only "\t", "\r" and "\n" characters are automatically escaped by i69n.
message: "Let's\ngo!" // quotes needed by YAML, \n is converted to new line by YAML
...
String get message => "Let's\ngo!"; // new line escaped by i69n -> result is the same string
copied to clipboard
If you need to disable escaping, use "noescape" flag.
_i69n: noescape
message: Let's go!
copied to clipboard
Escaping the details of escaping #
These are a few examples of more complicated strings in which you might need to add a backslash here and there:
problematic: "Hello \\\"world\\\"!"
// ^^^ yes, tripple backslash
lessProblematic: 'Hello \"world\"!'
// ^ use single quotes in YAML, add only one backslash for Dart
alsoProblematic(int count): "${_plural(count, zero:'didn\\'t find any tasks', other: 'found some tasks')}"
// ^ here
copied to clipboard
Please prefer single quotes inside pluralization functions.
But in most cases you will be fine. Just observe generated classes and maybe experiment a little bit.
More configuration flags #
So far only 'noescape' and 'nomap'. If you need both, separate them with comma.
_i69n: noescape,nomap
copied to clipboard
Custom pluralization #
The package can correctly decide between 'one', 'few', 'many', etc. only for
English and Czech (for now). But you can easily plug your own language,
see example/main.dart
and Czech and English
implementation.
If you implement support for your language, please let me know,
I'll gladly embed it into the package: [email protected]
Language of the default file #
The default file (the one without language code in the file name) is supposed to be in English (meaning English locales are used in pluralization functions). You can change that by adding a 'language' to the file:
_i69n_language: de
generic:
done: Erledigt
ok: OK
copied to clipboard
The '_i69n_language' flag is only meaningful on the first line of the default file, there is no reason to use it anywhere else.
Custom imports and custom types #
It might be usefull to teach your message objects some tyep control, for example:
friends:
michael:
name: Michael Friend
description: My good friend.
eva:
name: Eva Friend
description: My not so good friend.
copied to clipboard
It's obvious that 'friends' should implement some kind of interface so you can work with them in a type-safe way. You can achieve that by adding a 'implements' flag:
friends:
michael:
_i69n_implements: MyFriendInterface
name: Michael Friend
description: My good friend.
eva:
_i69n_implements: MyFriendInterface
name: Eva Friend
description: My not so good friend.
copied to clipboard
In most case it should be enough to add 'implements' flag in the default messages file only.
And you will probably need to import that interface. Just put 'import' flag at the beginning of the file:
_i69n_import: package:my_app/my_friend_interface.dart
copied to clipboard
And you can use the import flag to import any other file (for example with some kind of number formatting). Separate multiple imports by comma.
_i69n_import: package:my_app/my_friend_interface.dart,package:my_app/my_number_formatting.dart
copied to clipboard
Example #
See example. Clone the package repository (https://github.com/fnx-io/i69n) and run:
webdev serve example:8080
copied to clipboard
or
dart run build_runner serve example:8080
copied to clipboard
Now open the browser http://localhost:8080/ and watch the dev tools console.
Credits #
Created by https://fnx.io.
See also: #
https://pub.dev/packages/preconditions
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.