diff --git a/README.md b/README.md index 1745fd64..a68752f7 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,38 @@ simplify the internationalizing process in Flutter . Internationalization by Using JSON Files . ## Changelog +### [1.3.0] + - Load translations from remote or backend + - fixed many issues +### [1.2.1] + - supported shared_preferences + - Save selected localization +### [1.2.0] + - Added property resolver for nested key translations + - return translate key if the element or path not exist + ``` +{ + "title": "Hello", + "msg": "Hello {} in the {} world ", + "clickMe": "Click me", + "profile": { + "reset_password": { + "title": "Reset Password", + "username": "Username", + "password": "password" + } + }, + "clicked": { + "zero": "You clicked {} times!", + "one": "You clicked {} time!", + "other": "You clicked {} times!" + } +} + +new Text( + AppLocalizations.of(context).tr('profile.reset_password.title'), + ), +``` ### [1.0.4] - Added Support country codes ### [1.0.3] @@ -32,9 +64,10 @@ easy_localization: ``` +#### Load translations from local assets : -you must create a folder in your project's root: the `path`. Some examples: +You must create a folder in your project's root: the `path`. Some examples: > /assets/"langs" , "i18n", "locale" or anyname ... > @@ -83,10 +116,144 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, //app-specific localization EasylocaLizationDelegate( - locale: data.locale ?? Locale('en'), path: 'resources/langs'), + locale: data.locale, + path: 'resources/langs'), + ], + supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')], + locale: data.savedLocale, + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Easy localization'), + ), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int counter = 0; + incrementCounter() { + setState(() { + counter++; + }); + } + + @override + Widget build(BuildContext context) { + var data = EasyLocalizationProvider.of(context).data; + return EasyLocalizationProvider( + data: data, + child: Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context).tr('title')), + actions: [ + FlatButton( + child: Text("English"), + color: Localizations.localeOf(context).languageCode == "en" + ? Colors.lightBlueAccent + : Colors.blue, + onPressed: () { + this.setState(() { + data.changeLocale(Locale("en","US")); + print(Localizations.localeOf(context).languageCode); + }); + }, + ), + FlatButton( + child: Text("عربي"), + color: Localizations.localeOf(context).languageCode == "ar" + ? Colors.lightBlueAccent + : Colors.blue, + onPressed: () { + this.setState(() { + data.changeLocale(Locale("ar","DZ")); + print(Localizations.localeOf(context).languageCode); + }); + }, + ) + ], + ), + body: Center( + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text(AppLocalizations.of(context) + .tr('msg', args: ['aissat', 'Flutter'])), + new Text(AppLocalizations.of(context).plural('clicked', counter)), + new FlatButton( + onPressed: () async { + incrementCounter(); + }, + child: new Text(AppLocalizations.of(context).tr('clickMe')), + ), + new Text( + AppLocalizations.of(context).tr('profile.reset_password.title'), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: incrementCounter, + child: Text('+1'), + ), + ), + ); + } +} +``` + +#### Load translations from backend : + +You need to have backend endpoint (`loadPath`) where resources get loaded from and your endpoint must containing the translated keys. + + +example: + +```dart +String loadPath = 'https://raw.githubusercontent.com/aissat/easy_localization/master/example/resources/langs' +``` +> '${`loadPath`}/${languageCode}-${countryCode}.json' + + - 'https://raw.githubusercontent.com/aissat/easy_localization/master/example/resources/langs/en-US.json' + - 'https://raw.githubusercontent.com/aissat/easy_localization/master/example/resources/langs/ar-DZ.json' + + +The next step : + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:easy_localization/easy_localization.dart'; + +void main() => runApp(EasyLocalization(child: MyApp())); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + var data = EasyLocalizationProvider.of(context).data; + return EasyLocalizationProvider( + data: data, + child: MaterialApp( + title: 'Flutter Demo', + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + //app-specific localization + EasylocaLizationDelegate( + locale: data.locale, + loadPath: 'https://raw.githubusercontent.com/aissat/easy_localization/master/example/resources/langs'), ], - supportedLocales: [Locale('en'), Locale('ar')], - locale: data.locale, + supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')], + locale: data.savedLocale, theme: ThemeData( primarySwatch: Colors.blue, ), @@ -106,12 +273,13 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int counter = 0; + int counter = 0; incrementCounter() { setState(() { counter++; }); } + @override Widget build(BuildContext context) { var data = EasyLocalizationProvider.of(context).data; @@ -128,19 +296,19 @@ class _MyHomePageState extends State { : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("en")); + data.changeLocale(Locale("en","US")); print(Localizations.localeOf(context).languageCode); }); }, ), FlatButton( - child: Text("عربى"), + child: Text("عربي"), color: Localizations.localeOf(context).languageCode == "ar" ? Colors.lightBlueAccent : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("ar")); + data.changeLocale(Locale("ar","DZ")); print(Localizations.localeOf(context).languageCode); }); }, @@ -151,18 +319,25 @@ class _MyHomePageState extends State { child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - new Text(AppLocalizations.of(context).tr('msg',args: ['aissat','flutter'])), - new Text(AppLocalizations.of(context).plural('clicked',counter)), + new Text(AppLocalizations.of(context) + .tr('msg', args: ['aissat', 'Flutter'])), + new Text(AppLocalizations.of(context).plural('clicked', counter)), new FlatButton( - onPressed: () async { - incrementCounter(); - }, - child: new Text(AppLocalizations.of(context).tr('clickMe')),) - + onPressed: () async { + incrementCounter(); + }, + child: new Text(AppLocalizations.of(context).tr('clickMe')), + ), + new Text( + AppLocalizations.of(context).tr('profile.reset_password.title'), + ), ], ), ), - floatingActionButton: FloatingActionButton(onPressed: incrementCounter,child: Text('+1'),), + floatingActionButton: FloatingActionButton( + onPressed: incrementCounter, + child: Text('+1'), + ), ), ); } @@ -178,3 +353,21 @@ class _MyHomePageState extends State { English LTR + + +Donations +--------- + +This project needs you! If you would like to support this project's further development, the creator of this project or the continuous maintenance of this project, feel free to donate. Your donation is highly appreciated (and I love food and coffee). Thank you! +**PayPal** + +* **[Donate $5](https://paypal.me/aissatabdelwahab/5)**: Thank's for creating this project, here's a coffee for you! +* **[Donate $10](https://paypal.me/aissatabdelwahab/10)**: Wow, I am stunned. Let me take you to the movies! +* **[Donate $15](https://paypal.me/aissatabdelwahab/15)**: I really appreciate your work, let's grab some lunch! +* **[Donate $25](https://paypal.me/aissatabdelwahab/25)**: That's some awesome stuff you did right there, dinner is on me! +Of course, you can also choose what you want to donate, all donations are awesome! +## Contributors thanks! + + - [iwansugiarto](https://github.com/javico2609) + - [javico2609](https://github.com/iwansugiarto) + - [Taym95](https://github.com/Taym95) \ No newline at end of file diff --git a/example/README.md b/example/README.md index 815db439..89ef7a2d 100644 --- a/example/README.md +++ b/example/README.md @@ -7,6 +7,13 @@ "title": "السلام", "msg":"السلام عليكم يا {} في عالم {}", "clickMe":"إضغط هنا", + "profile": { + "reset_password": { + "title": "اعادة تعين كلمة السر", + "username": "المستخدم", + "password": "كلمة السر" + } + }, "clicked": { "zero": "{} نقرة!", "one": "{} نقرة!", @@ -18,14 +25,22 @@ ```json { "title": "Hello", - "msg":"Hello {} in the {} world ", - "clickMe":"Click me", + "msg": "Hello {} in the {} world ", + "clickMe": "Click me", + "profile": { + "reset_password": { + "title": "Reset Password", + "username": "Username", + "password": "password" + } + }, "clicked": { "zero": "You clicked {} times!", "one": "You clicked {} time!", "other": "You clicked {} times!" } } + ``` ### example/lib/main.dart @@ -49,10 +64,11 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, //app-specific localization EasylocaLizationDelegate( - locale: data.locale ?? Locale('en','US'), path: 'resources/langs'), + locale: data.locale, + path: 'resources/langs'), ], - supportedLocales: [Locale('en','US'), Locale('ar','DZ')], - locale: data.locale, + supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')], + locale: data.savedLocale, theme: ThemeData( primarySwatch: Colors.blue, ), @@ -72,12 +88,13 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int counter = 0; + int counter = 0; incrementCounter() { setState(() { counter++; }); } + @override Widget build(BuildContext context) { var data = EasyLocalizationProvider.of(context).data; @@ -94,19 +111,19 @@ class _MyHomePageState extends State { : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("en")); + data.changeLocale(Locale("en","US")); print(Localizations.localeOf(context).languageCode); }); }, ), FlatButton( - child: Text("عربى"), + child: Text("عربي"), color: Localizations.localeOf(context).languageCode == "ar" ? Colors.lightBlueAccent : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("ar")); + data.changeLocale(Locale("ar","DZ")); print(Localizations.localeOf(context).languageCode); }); }, @@ -117,20 +134,28 @@ class _MyHomePageState extends State { child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - new Text(AppLocalizations.of(context).tr('msg',args: ['aissat','Flutter'])), - new Text(AppLocalizations.of(context).plural('clicked',counter)), + new Text(AppLocalizations.of(context) + .tr('msg', args: ['aissat', 'Flutter'])), + new Text(AppLocalizations.of(context).plural('clicked', counter)), new FlatButton( - onPressed: () async { - incrementCounter(); - }, - child: new Text(AppLocalizations.of(context).tr('clickMe')),) - + onPressed: () async { + incrementCounter(); + }, + child: new Text(AppLocalizations.of(context).tr('clickMe')), + ), + new Text( + AppLocalizations.of(context).tr('profile.reset_password.title'), + ), ], ), ), - floatingActionButton: FloatingActionButton(onPressed: incrementCounter,child: Text('+1'),), + floatingActionButton: FloatingActionButton( + onPressed: incrementCounter, + child: Text('+1'), + ), ), ); } } + ``` diff --git a/example/lib/main.dart b/example/lib/main.dart index c9be3f2e..2e83fc6f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,10 +17,13 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, //app-specific localization EasylocaLizationDelegate( - locale: data.locale ?? Locale('en','US'), path: 'resources/langs'), + locale: data.locale, + path: 'resources/langs', + // loadPath: 'https://raw.githubusercontent.com/aissat/easy_localization/master/example/resources/langs' + ), ], - supportedLocales: [Locale('en','US'), Locale('ar','DZ')], - locale: data.locale, + supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ')], + locale: data.savedLocale, theme: ThemeData( primarySwatch: Colors.blue, ), @@ -40,12 +43,13 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int counter = 0; + int counter = 0; incrementCounter() { setState(() { counter++; }); } + @override Widget build(BuildContext context) { var data = EasyLocalizationProvider.of(context).data; @@ -62,19 +66,19 @@ class _MyHomePageState extends State { : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("en")); + data.changeLocale(Locale("en", "US")); print(Localizations.localeOf(context).languageCode); }); }, ), FlatButton( - child: Text("عربى"), + child: Text("عربي"), color: Localizations.localeOf(context).languageCode == "ar" ? Colors.lightBlueAccent : Colors.blue, onPressed: () { this.setState(() { - data.changeLocale(Locale("ar")); + data.changeLocale(Locale("ar", "DZ")); print(Localizations.localeOf(context).languageCode); }); }, @@ -85,18 +89,25 @@ class _MyHomePageState extends State { child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - new Text(AppLocalizations.of(context).tr('msg',args: ['aissat','Flutter'])), - new Text(AppLocalizations.of(context).plural('clicked',counter)), + new Text(AppLocalizations.of(context) + .tr('msg', args: ['aissat', 'Flutter'])), + new Text(AppLocalizations.of(context).plural('clicked', counter)), new FlatButton( - onPressed: () async { - incrementCounter(); - }, - child: new Text(AppLocalizations.of(context).tr('clickMe')),) - + onPressed: () async { + incrementCounter(); + }, + child: new Text(AppLocalizations.of(context).tr('clickMe')), + ), + new Text( + AppLocalizations.of(context).tr('profile.reset_password.title'), + ), ], ), ), - floatingActionButton: FloatingActionButton(onPressed: incrementCounter,child: Text('+1'),), + floatingActionButton: FloatingActionButton( + onPressed: incrementCounter, + child: Text('+1'), + ), ), ); } diff --git a/example/resources/langs/ar-DZ.json b/example/resources/langs/ar-DZ.json index e9ae01b4..8d455120 100644 --- a/example/resources/langs/ar-DZ.json +++ b/example/resources/langs/ar-DZ.json @@ -2,6 +2,13 @@ "title": "السلام", "msg":"السلام عليكم يا {} في عالم {}", "clickMe":"إضغط هنا", + "profile": { + "reset_password": { + "title": "اعادة تعين كلمة السر", + "username": "المستخدم", + "password": "كلمة السر" + } + }, "clicked": { "zero": "{} نقرة!", "one": "{} نقرة!", diff --git a/example/resources/langs/en-US.json b/example/resources/langs/en-US.json index 02cce302..95b16e86 100644 --- a/example/resources/langs/en-US.json +++ b/example/resources/langs/en-US.json @@ -1,10 +1,17 @@ { "title": "Hello", - "msg":"Hello {} in the {} world ", - "clickMe":"Click me", + "msg": "Hello {} in the {} world ", + "clickMe": "Click me", + "profile": { + "reset_password": { + "title": "Reset Password", + "username": "Username", + "password": "password" + } + }, "clicked": { "zero": "You clicked {} times!", "one": "You clicked {} time!", "other": "You clicked {} times!" } -} \ No newline at end of file +} diff --git a/lib/easy_localization_delegate.dart b/lib/easy_localization_delegate.dart index e81d6bca..d7f8b048 100644 --- a/lib/easy_localization_delegate.dart +++ b/lib/easy_localization_delegate.dart @@ -1,12 +1,15 @@ import 'dart:convert'; import 'package:flutter/widgets.dart'; import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; class AppLocalizations { - AppLocalizations(this.locale, this.path); + AppLocalizations(this.locale, {this.path, this.loadPath}); Locale locale; final String path; + final String loadPath; static AppLocalizations of(BuildContext context) { return Localizations.of(context, AppLocalizations); @@ -16,11 +19,28 @@ class AppLocalizations { Future load() async { String data; - if (this.locale.languageCode == null || this.locale.countryCode == null) { - this.locale = Locale("en", "US"); + + final SharedPreferences _preferences = + await SharedPreferences.getInstance(); + + var _codeLang = _preferences.getString('codeL'); + var _codeCoun = _preferences.getString('codeC'); + // if (_codeLang == null || _codeCoun == null) { + // this.locale = Locale(this.locale.languageCode, + // this.locale.countryCode); // Locale("en", "US"); + // await _preferences.setString('codeC', this.locale.countryCode); + // await _preferences.setString('codeL', this.locale.languageCode); + // } + this.locale = Locale(_codeLang, _codeCoun); + + if (path != null) { + data = await rootBundle.loadString('$path/$_codeLang-$_codeCoun.json'); + } else if (loadPath != null) { + data = await http + .get('$loadPath/$_codeLang-$_codeCoun.json') + .then((response) => response.body.toString()); } - data = await rootBundle.loadString( - '$path/${this.locale.languageCode}-${this.locale.countryCode}.json'); + Map _result = json.decode(data); this._sentences = new Map(); @@ -32,7 +52,7 @@ class AppLocalizations { } String tr(String key, {List args}) { - String res = this._sentences[key].toString(); + String res = this._resolve(key, this._sentences); if (args != null) { args.forEach((String str) { res = res.replaceFirst(RegExp(r'{}'), str); @@ -52,22 +72,50 @@ class AppLocalizations { } return res.replaceFirst(RegExp(r'{}'), '$value'); } + + String _resolve(String path, dynamic obj) { + List keys = path.split('.'); + + if (keys.length > 1) { + for (int index = 0; index <= keys.length; index++) { + if (obj.containsKey(keys[index]) && obj[keys[index]] is! String) { + return _resolve( + keys.sublist(index + 1, keys.length).join('.'), obj[keys[index]]); + } + + return obj[path] ?? path; + } + } + + return obj[path] ?? path; + } } class EasylocaLizationDelegate extends LocalizationsDelegate { final Locale locale; final String path; + final String loadPath; - EasylocaLizationDelegate({@required this.locale, @required this.path}); + EasylocaLizationDelegate({@required this.locale, this.path, this.loadPath}); @override bool isSupported(Locale locale) => locale != null; @override - Future load(Locale locale) async { - AppLocalizations localizations = AppLocalizations(locale, path); + Future load(Locale value) async { + final SharedPreferences _preferences = + await SharedPreferences.getInstance(); + var _codeLang = _preferences.getString('codeL'); + var _codeCoun = _preferences.getString('codeC'); + if (_codeLang == null || _codeCoun == null) { + //value = Locale(this.locale.languageCode, this.locale.countryCode); + await _preferences.setString('codeC', value.countryCode); + await _preferences.setString('codeL', value.languageCode); + } else + value = Locale(_codeLang, _codeCoun); + AppLocalizations localizations = + AppLocalizations(value, path: path, loadPath: loadPath); await localizations.load(); - // print("Load ${locale.languageCode}"); return localizations; } diff --git a/lib/easy_localization_provider.dart b/lib/easy_localization_provider.dart index 31ae3a15..6ac4e44a 100644 --- a/lib/easy_localization_provider.dart +++ b/lib/easy_localization_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class EasyLocalizationProvider extends InheritedWidget { EasyLocalizationProvider({Key key, this.child, this.data}) @@ -25,20 +26,43 @@ class EasyLocalization extends StatefulWidget { class _EasyLocalizationState extends State { Locale _locale; + Locale _savedLocale; Locale get locale => _locale; + Locale get savedLocale => _savedLocale; + @override + void initState() { + super.initState(); + saveLocale(); + } - void changeLocale(Locale value) { + void changeLocale(Locale value) async { + final SharedPreferences preferences = await SharedPreferences.getInstance(); + await preferences.setString('codeC', value.countryCode); + await preferences.setString('codeL', value.languageCode); + var _codeLang = preferences.getString('codeL'); + var _codeCoun = preferences.getString('codeC'); setState(() { - _locale = value; + _locale = Locale(_codeLang, _codeCoun); + _savedLocale = Locale(_codeLang, _codeCoun); }); } - @override - Widget build(BuildContext context) { - return EasyLocalizationProvider( - data: this, - child: widget.child, - ); + void saveLocale() async { + final SharedPreferences _preferences = + await SharedPreferences.getInstance(); + var _codeLang = _preferences.getString('codeL'); + var _codeCoun = _preferences.getString('codeC'); + if (_codeLang != null || _codeCoun != null) { + setState(() { + _savedLocale = Locale(_codeLang, _codeCoun); + }); + } } + + @override + Widget build(BuildContext context) => EasyLocalizationProvider( + data: this, + child: widget.child, + ); } diff --git a/pubspec.lock b/pubspec.lock index 1850d7a1..5bc53cb7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" charcode: dependency: transitive description: @@ -44,6 +44,20 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+2" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" intl: dependency: transitive description: @@ -78,7 +92,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0+1" quiver: dependency: transitive description: @@ -86,6 +100,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.3" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.3+4" sky_engine: dependency: transitive description: flutter @@ -149,3 +170,4 @@ packages: version: "2.0.8" sdks: dart: ">=2.2.2 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4d15a226..24dc38a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: easy_localization description: Easy and Fast internationalizing and localization your Flutter Apps, this package simplify the internationalizing process using Json file. -version: 1.0.4 +version: 1.3.0 author: AISSAT abdelwahab homepage: https://github.com/aissat/easy_localization @@ -12,7 +12,10 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - + http: ^0.12.0+2 + + shared_preferences: ^0.5.3+4 + dev_dependencies: flutter_test: sdk: flutter diff --git a/screenshots/Screenshot_ar.png b/screenshots/Screenshot_ar.png old mode 100644 new mode 100755 index 5bee5dc3..39be6681 Binary files a/screenshots/Screenshot_ar.png and b/screenshots/Screenshot_ar.png differ diff --git a/screenshots/Screenshot_en.png b/screenshots/Screenshot_en.png old mode 100644 new mode 100755 index 033bec28..aefb3ceb Binary files a/screenshots/Screenshot_en.png and b/screenshots/Screenshot_en.png differ