-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit cec3210
Showing
8 changed files
with
3,131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.dart_tool | ||
.idea | ||
.packages |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 serenader | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
# Dart Gettext | ||
|
||
A Dart implementation of gettext, a localization framework. | ||
|
||
Rewritten to dart of [node-gettext](https://github.com/alexanderwallin/node-gettext) package. | ||
|
||
Also works perfectly with flutter. | ||
|
||
## Features | ||
|
||
- Supports domains, contexts and plurals | ||
- Ships with plural forms for 136 languages | ||
- Change locale or domain on the fly | ||
- Emits events for internal errors, such as missing translations | ||
|
||
|
||
## Usage | ||
|
||
```dart | ||
import "package:gettext/gettext.dart"; | ||
final gt = new Gettext(onWarning: print); | ||
``` | ||
|
||
Load data from json: | ||
```dart | ||
new File("./en_US.json").readAsString().then( | ||
(data) { | ||
final json = json.decode(data["translations"]); | ||
final gt = new Gettext(); | ||
gt.addTranslations('en_US', Translations.fromJSON(json)); | ||
} | ||
); | ||
``` | ||
|
||
Json content: | ||
```json | ||
{ | ||
"charset": "utf-8", | ||
"headers": { | ||
"mime-version": "1.0", | ||
"content-type": "text/plain; charset=utf-8", | ||
"content-transfer-encoding": "8bit", | ||
"language": "es-ES", | ||
"plural-forms": "nplurals=2; plural=(n!=1);", | ||
}, | ||
"translations": { | ||
"": { | ||
"Hello": { | ||
"msgid": "Hello", | ||
"comments": { | ||
"translator": "Normal string" | ||
}, | ||
"msgstr": [ | ||
"Hola" | ||
] | ||
}, | ||
"An apple": { | ||
"msgid": "1 apple", | ||
"comments": { | ||
"translator": "Plural string" | ||
}, | ||
"msgstr": [ | ||
"una manzana", | ||
"%d manzanas" | ||
] | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Change locale: | ||
```dart | ||
gt.locale = "en_US"; | ||
``` | ||
|
||
Change default domain: | ||
```dart | ||
gt.domain = "types"; | ||
``` | ||
|
||
Translate messages: | ||
```dart | ||
gt.gettext("Hello"); | ||
``` | ||
|
||
Translate messages with plural forms: | ||
```dart | ||
gt.ngettext("An apple", "%d apples", 3); // returns "%d apples" | ||
``` | ||
|
||
|
||
## API | ||
|
||
- gettext(String msgid, {String domain, String context}) → `String` | ||
- ngettext(String msgid, String msgplural, int count, {String domain, String context}) → `String` | ||
|
||
## Working with Flutter | ||
|
||
```dart | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:sprintf/sprintf.dart'; | ||
import 'package:gettext/gettext.dart'; | ||
class GettextLocalizations { | ||
final Gettext gt; | ||
GettextLocalizations(this.gt); | ||
static GettextLocalizations of(BuildContext context) { | ||
return Localizations.of<GettextLocalizations>( | ||
context, GettextLocalizations); | ||
} | ||
String __( | ||
String msgid, [ | ||
List args = const [], | ||
]) { | ||
return sprintf(gt.gettext(msgid), args); | ||
} | ||
String __n( | ||
String msgid, | ||
String msgidPlural, | ||
int count, [ | ||
List arg = const [], | ||
]) { | ||
return sprintf( | ||
gt.ngettext(msgid, msgidPlural, count), | ||
<dynamic>[count]..addAll(arg), | ||
); | ||
} | ||
} | ||
class GettextLocalizationsDelegate | ||
extends LocalizationsDelegate<GettextLocalizations> { | ||
final gettext = new Gettext(); | ||
@override | ||
bool isSupported(Locale locale) => true; | ||
@override | ||
bool shouldReload(LocalizationsDelegate<GettextLocalizations> old) => false; | ||
@override | ||
Future<GettextLocalizations> load(Locale locale) { | ||
return loadData(locale).then((data) { | ||
gettext.locale = locale.toString(); | ||
gettext.addTranslations(locale.toString(), Translations.fromJson(data)); | ||
return GettextLocalizations(gettext); | ||
}); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
library gettext; | ||
|
||
import 'plurals.dart'; | ||
|
||
typedef OnWarning(String message); | ||
|
||
class Gettext { | ||
final Map<String, Catalog> catalogs = new Map(); | ||
final OnWarning onWarning; | ||
|
||
Gettext({this.onWarning}); | ||
|
||
Function _pluralsFunc = (n) => 0; | ||
|
||
String _locale = ''; | ||
String _domain = 'messages'; | ||
|
||
String get locale => _locale; | ||
|
||
set locale(String value) { | ||
_locale = value ?? ''; | ||
_pluralsFunc = plurals[getLanguageCode(_locale)]?.pluralsFunc; | ||
} | ||
|
||
String get domain => _domain; | ||
|
||
set domain(String value) => _locale = value ?? ''; | ||
|
||
/** | ||
* Stores a set of translations in the set of gettext catalogs. | ||
* | ||
* @example | ||
* gt.addTranslations('sv-SE', translations, domain: 'messages') | ||
* | ||
* @param {String} locale A locale string | ||
* @param {String} domain A domain name | ||
* @param {Object} translations An object of gettext-parser JSON shape | ||
*/ | ||
void addTranslations(String locale, Translations translations, | ||
{String domain: 'messages'}) { | ||
if (!catalogs.containsKey(locale)) { | ||
catalogs[locale] = new Catalog({}); | ||
} | ||
|
||
catalogs[locale].addTranslations(domain, translations); | ||
|
||
if (_locale.isEmpty) { | ||
this.locale = locale; | ||
} | ||
} | ||
|
||
/** | ||
* Translates a string using the default textdomain | ||
* | ||
* @example | ||
* gt.gettext('Some text') | ||
* | ||
* @param {String} msgid String to be translated | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
String gettext(String msgid, {String domain, String msgctxt = ''}) { | ||
final translation = this._getTranslation( | ||
domain ?? this.domain, | ||
msgctxt, | ||
msgid, | ||
); | ||
|
||
if (translation == null || translation.msgstr[0]?.isNotEmpty != true) { | ||
_warn('No translation was found for ' | ||
'msgid "$msgid" in msgctxt "$msgctxt" and domain "$domain"'); | ||
return msgid; | ||
} | ||
|
||
return translation.msgstr[0]; | ||
} | ||
|
||
/** | ||
* Translates a plural string using the default textdomain | ||
* | ||
* @example | ||
* gt.ngettext('One thing', 'Many things', numberOfThings) | ||
* | ||
* @param {String} msgid String to be translated when count is not plural | ||
* @param {String} msgidPlural String to be translated when count is plural | ||
* @param {Number} count Number count for the plural | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
String ngettext( | ||
String msgid, | ||
String msgidPlural, | ||
int count, { | ||
String domain, | ||
String msgctxt = '', | ||
}) { | ||
final translation = | ||
this._getTranslation(domain ?? this.domain, msgctxt, msgid); | ||
|
||
final index = _pluralsFunc(count); | ||
|
||
if (translation == null || | ||
translation.msgstr.length <= index || | ||
translation.msgstr[index]?.isNotEmpty != true) { | ||
_warn('No translation was found for ' | ||
'msgid "$msgid" in msgctxt "$msgctxt" and domain "$domain"'); | ||
return (count > 1) ? msgidPlural ?? msgid : msgid; | ||
} | ||
|
||
return translation.msgstr[index]; | ||
} | ||
|
||
/** | ||
* Retrieves translation object from the domain and context | ||
* | ||
* @private | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {Translation} Translation object or null if not found | ||
*/ | ||
Translation _getTranslation(String domain, String msgctxt, String msgid) { | ||
assert(domain != null); | ||
assert(msgctxt != null); | ||
assert(msgid != null); | ||
|
||
return catalogs[_locale]?.getTranslation(domain, msgctxt, msgid); | ||
} | ||
|
||
/** | ||
* Returns the language code part of a locale | ||
* | ||
* @example | ||
* Gettext.getLanguageCode('sv-SE') | ||
* // -> "sv" | ||
* | ||
* @private | ||
* @param {String} locale A case-insensitive locale string | ||
* @returns {String} A language code | ||
*/ | ||
static String getLanguageCode(String locale) { | ||
return locale.split(RegExp(r'[\-_]'))[0].toLowerCase(); | ||
} | ||
|
||
_warn(String message) { | ||
if (onWarning != null) { | ||
onWarning(message); | ||
} | ||
} | ||
} | ||
|
||
class Catalog { | ||
final Map<String, Translations> domains; | ||
|
||
Catalog(this.domains); | ||
|
||
Translation getTranslation(String domain, String msgctxt, String msgid) { | ||
return this.domains[domain]?.getTranslation(msgctxt, msgid); | ||
} | ||
|
||
void addTranslations(String domain, Translations translations) { | ||
domains[domain] = translations; | ||
} | ||
} | ||
|
||
class Translations { | ||
final Map<String, Map<String, Translation>> contexts; | ||
|
||
Translations(this.contexts); | ||
|
||
Translation getTranslation(String msgctxt, String msgid) { | ||
if (this.contexts[msgctxt] == null) { | ||
return null; | ||
} | ||
|
||
return this.contexts[msgctxt][msgid]; | ||
} | ||
|
||
static Translations fromJson(Map<String, dynamic> contexts, | ||
{Map<String, String> headers}) { | ||
return new Translations( | ||
contexts.map((key, value) { | ||
final values = new Map<String, Translation>(); | ||
|
||
if (value is Map<String, Map<String, dynamic>>) { | ||
values.addAll( | ||
value.map( | ||
(msgid, data) => MapEntry<String, Translation>( | ||
msgid, | ||
Translation.fromJSON(data), | ||
), | ||
), | ||
); | ||
} | ||
|
||
return MapEntry<String, Map<String, Translation>>(key, values); | ||
}), | ||
); | ||
} | ||
} | ||
|
||
class Translation { | ||
final List<String> msgstr; | ||
final Map<String, String> comments; | ||
|
||
Translation(this.msgstr, {this.comments}) : assert(msgstr != null); | ||
|
||
static Translation fromJSON(Map<String, dynamic> json) { | ||
return new Translation(json["msgstr"], comments: json["comments"]); | ||
} | ||
} |
Oops, something went wrong.