Skip to content

Commit

Permalink
First implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cah4a committed Nov 11, 2018
0 parents commit cec3210
Show file tree
Hide file tree
Showing 8 changed files with 3,131 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dart_tool
.idea
.packages
21 changes: 21 additions & 0 deletions LICENSE
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.
155 changes: 155 additions & 0 deletions README.md
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);
});
}
}
```
209 changes: 209 additions & 0 deletions lib/gettext.dart
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"]);
}
}
Loading

0 comments on commit cec3210

Please sign in to comment.