Skip to content

Commit

Permalink
Merge pull request #76 from defint/namespace-file-loader
Browse files Browse the repository at this point in the history
Namespace file loader
  • Loading branch information
ilteoood authored Mar 16, 2020
2 parents d379ca4 + a5b8c8a commit f242b60
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 33 deletions.
56 changes: 53 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# flutter_i18n
I18n made easy, for Flutter!

<!-- Badges -->
[![Pub Package](https://img.shields.io/pub/v/flutter_i18n.svg)](https://pub.dev/packages/flutter_i18n)
[![GitHub Actions](https://github.com/ilteoood/flutter_i18n/workflows/Publish%20plugin/badge.svg)](https://github.com/ilteoood/flutter_i18n/actions)

------------------------------------------------


Expand All @@ -19,10 +23,11 @@ You can easy override loader and create your own.
Available loaders:

| Class name | Purpose |
| - | - |
| --- | --- |
| `FileTranslationLoader` | Loads translation files from JSON or YAML format |
| `NetworkFileTranslationLoader` | Loads translations from the remote resource |
| `E2EFileTranslation loader` | Special loader for solving isolates problem with flutter drive |
| `NamespaceFileTranslationLoader` | Loads translations from separate files |
| `E2EFileTranslationLoader` | Special loader for solving isolates problem with flutter drive |

### `FileTranslationLoader` configuration

Expand Down Expand Up @@ -80,7 +85,7 @@ If there isn't any translation available for the required key, the same key is r

### `NetworkFileTranslationLoader` configuration

Behaviour of this loader very similar as `FileTranslationLoader`. The main different that we load translations from `NetworkAssetBundle` instead of `CachingAssetBundle`.
Behaviour of this loader very similar as `FileTranslationLoader`. The main difference that we load translations from `NetworkAssetBundle` instead of `CachingAssetBundle`.

Below you can find the name and description of the accepted parameters.

Expand All @@ -106,6 +111,51 @@ localizationsDelegates: [
],
```

### `NamespaceFileTranslationLoader` configuration

Behaviour of this loader very similar as `FileTranslationLoader`. The main difference that we load translations from separate files per each language.

For example `FileTranslationLoader` format:

> /assets/flutter_i18n/en.json
>
> /assets/flutter_i18n/it.json
`NamespaceFileTranslationLoader` format:

> /assets/flutter_i18n/en/home_screen.json
>
> /assets/flutter_i18n/en/about_screen.json
>
> /assets/flutter_i18n/it/home_screen.json
>
> /assets/flutter_i18n/it/about_screen.json
Example configuration:

```dart
localizationsDelegates: [
FlutterI18nDelegate(translationLoader:
NamespaceFileTranslationLoader(namespaces: ["home_screen", "about_screen"]),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
```

Below you can find the name and description of the accepted parameters.

The ***namespaces*** provide a list of filenames for the specific language directory.

The ***useCountryCode*** parameter depends on the *json* configuration:
- if you used the pattern {languageCode}_{countryCode}, ***useCountryCode*** must be **true**
- if you used the pattern {languageCode}, ***useCountryCode*** must be **false**

The ***fallbackDir*** provide a default language directory, used when the translation for the current running system is not provided.

The ***basePath*** parameter is optionally used to set the base path for translations. If this option is not set, the default path will be `assets/flutter_i18n`. This path must be the same path as the one defined in your ***pubspec.yaml***.

The ***forcedLocale*** parameter is optionally used to force a locale instead finding the system one.

### `E2EFileTranslationLoader` configuration

The same as `FileTranslationLoader` configuration. This loader can be used for solving problem with flutter drive testing.
Expand Down
3 changes: 3 additions & 0 deletions example/assets/i18n_namespace/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "Namespace example"
}
2 changes: 2 additions & 0 deletions example/assets/i18n_namespace/en/home.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label:
main: "Label from another namespace"
3 changes: 3 additions & 0 deletions example/assets/i18n_namespace/ua/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "Приклад простору імен"
}
5 changes: 5 additions & 0 deletions example/assets/i18n_namespace/ua/home.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"label": {
"main": "Напис з іншого простору імен"
}
}
7 changes: 7 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';

import 'basic_example.dart' as basicExample;
import 'namespace_example.dart' as namespaceExample;
import 'network_example.dart' as networkExample;

Future main() async {
Expand Down Expand Up @@ -38,6 +39,12 @@ class MyApp extends StatelessWidget {
},
child: Text("Run `network` example"),
),
RaisedButton(
onPressed: () {
namespaceExample.main();
},
child: Text("Run `namespace` example"),
),
],
));
})));
Expand Down
79 changes: 79 additions & 0 deletions example/lib/namespace_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

Future main() async {
final FlutterI18nDelegate flutterI18nDelegate = FlutterI18nDelegate(
translationLoader: NamespaceFileTranslationLoader(
namespaces: ["common", "home"],
useCountryCode: false,
fallbackDir: 'en',
basePath: 'assets/i18n_namespace',
forcedLocale: Locale('it')),
);
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp(flutterI18nDelegate));
}

class MyApp extends StatelessWidget {
final FlutterI18nDelegate flutterI18nDelegate;

MyApp(this.flutterI18nDelegate);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: MyHomePage(),
localizationsDelegates: [
flutterI18nDelegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
);
}
}

class MyHomePage extends StatefulWidget {
@override
MyHomeState createState() => MyHomeState();
}

class MyHomeState extends State<MyHomePage> {
changeLanguage() async {
final currentLang = FlutterI18n.currentLocale(context);
final nextLang =
currentLang.languageCode == 'ua' ? Locale('en') : Locale('ua');
await FlutterI18n.refresh(context, nextLang);
setState(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar:
AppBar(title: Text(FlutterI18n.translate(context, "common.title"))),
body: Builder(builder: (BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
I18nText("home.label.main", Text("")),
RaisedButton(
onPressed: () async {
await changeLanguage();
},
child: Text("Change language"))
],
),
);
}),
);
}
}
2 changes: 2 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ flutter:
uses-material-design: true
assets:
- assets/i18n/
- assets/i18n_namespace/en/
- assets/i18n_namespace/ua/

environment:
sdk: '>=1.19.0 <3.0.0'
1 change: 1 addition & 0 deletions lib/flutter_i18n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:flutter_i18n/utils/simple_translator.dart';
export 'flutter_i18n_delegate.dart';
export 'loaders/e2e_file_translation_loader.dart';
export 'loaders/file_translation_loader.dart';
export 'loaders/namespace_file_translation_loader.dart';
export 'loaders/network_file_translation_loader.dart';
export 'loaders/translation_loader.dart';
export 'widgets/I18nPlural.dart';
Expand Down
29 changes: 17 additions & 12 deletions lib/loaders/file_translation_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FileTranslationLoader extends TranslationLoader {
@override
set locale(Locale locale) => _locale = locale;

Map<dynamic, dynamic> _decodedMap;
Map<dynamic, dynamic> _decodedMap = Map();

FileTranslationLoader(
{this.fallbackFile = "en",
Expand All @@ -50,41 +50,46 @@ class FileTranslationLoader extends TranslationLoader {
Future _loadCurrentTranslation() async {
this.locale = locale ?? await findCurrentLocale();
MessagePrinter.info("The current locale is ${this.locale}");
await _loadFile(_composeFileName());
_decodedMap = await loadFile(composeFileName());
}

Future _loadFallback() async {
try {
await _loadFile(fallbackFile);
_decodedMap = await loadFile(fallbackFile);
} catch (e) {
MessagePrinter.debug('Error loading translation fallback $e');
_decodedMap = Map();
}
}

Future<void> _loadFile(final String fileName) async {
Future<Map> loadFile(final String fileName) async {
Map<dynamic, dynamic> result;

try {
await _decodeFile(fileName, 'json', json.decode);
result = await _decodeFile(fileName, 'json', json.decode);
MessagePrinter.info("JSON file loaded for $fileName");
} on Error catch (_) {
MessagePrinter.debug(
"Unable to load JSON file for $fileName, I'm trying with YAML");
await _decodeFile(fileName, 'yaml', loadYaml);
result = await _decodeFile(fileName, 'yaml', loadYaml);
MessagePrinter.info("YAML file loaded for $fileName");
}

return result;
}

Future<void> _decodeFile(final String fileName, final String extension,
Future<Map> _decodeFile(final String fileName, final String extension,
final Function decodeFunction) async {
_decodedMap = await loadString(fileName, extension)
return loadString(fileName, extension)
.then((fileContent) => decodeFunction(fileContent));
}

String _composeFileName() {
return "${locale.languageCode}${_composeCountryCode()}";
@protected
String composeFileName() {
return "${locale.languageCode}${composeCountryCode()}";
}

String _composeCountryCode() {
@protected
String composeCountryCode() {
String countryCode = "";
if (useCountryCode && locale.countryCode != null) {
countryCode = "_${locale.countryCode}";
Expand Down
58 changes: 58 additions & 0 deletions lib/loaders/namespace_file_translation_loader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/services.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/widgets.dart';
import 'package:flutter_i18n/loaders/file_translation_loader.dart';
import 'package:flutter_i18n/utils/message_printer.dart';

class NamespaceFileTranslationLoader extends FileTranslationLoader {
final String fallbackDir;
final String basePath;
final bool useCountryCode;
final Locale forcedLocale;
final List<String> namespaces;
AssetBundle assetBundle;

Map<dynamic, dynamic> _decodedMap = {};

NamespaceFileTranslationLoader(
{@required this.namespaces,
this.fallbackDir = "en",
this.basePath = "assets/flutter_i18n",
this.useCountryCode = false,
this.forcedLocale}) {
assert(namespaces != null);
assert(namespaces.length > 0);

assetBundle = rootBundle;
}

Future<Map> load() async {
this.locale = locale ?? await findCurrentLocale();
MessagePrinter.info("The current locale is ${this.locale}");

await Future.wait(
namespaces.map((namespace) => _loadTranslation(namespace)));

return _decodedMap;
}

Future<void> _loadTranslation(String namespace) async {
_decodedMap[namespace] = Map();

try {
_decodedMap[namespace] =
await loadFile("${composeFileName()}/$namespace");
} catch (e) {
MessagePrinter.debug('Error loading translation $e');
await _loadTranslationFallback(namespace);
}
}

Future<void> _loadTranslationFallback(String namespace) async {
try {
_decodedMap[namespace] = await loadFile("$fallbackDir/$namespace");
} catch (e) {
MessagePrinter.debug('Error loading translation fallback $e');
}
}
}
Loading

0 comments on commit f242b60

Please sign in to comment.