i18next-ios is a native iOS port of i18next.
In order to use the same translated strings for the Web, Android and iOS, we decided to use the i18next features and data formats.
This library is our implementation for iOS. We've been using it in production for a few years.
- Support for variables
- Support for nesting
- Support for context
- Support for multiple plural forms
- Gettext support
- Sprintf supported
- Detect language
- Graceful translation lookup
- Get string or object tree
- Get resourcefiles from server
- Resource caching
- Post missing resources to server
- Highly configurable
- Custom post processing
- Comprehensive unit test coverage
- iOS 7.0+
- Xcode 6.4
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, submit a pull request.
Installation with CocoaPods
platform :ios, '7.0'
pod 'i18next'
Create a new instance:
I18Next *i18n = [[I18Next alloc] init];
or get a shared instance:
I18Next *i18n = [I18Next sharedInstance];
If you use the shared instance you can use the handy I18NEXT
helper macro for getting translated values.
I18NextOptions* options = [I18NextOptions new];
options.lang = @"en-US";
Resources will be resolved in this order:
- try languageCode plus countryCode, eg. 'en-US'
- alternative look it up in languageCode only, eg. 'en'
- finally look it up in defined fallback language, default: 'dev'
I18NextOptions* options = [I18NextOptions new];
options.namespace = @"myNamespace";
// or multiple
options.namespaces = @[ @"myNamespace1", @"myNamespace2" ];
The additional namespace(s) will be loaded.
Specify the default namespace with options.defaultNamespace = @"myNamespace"
.
I18NextOptions* options = [I18NextOptions new];
options.fallbackLang = @"en";
If not set it will default to 'dev'. If turned on, all missing key/values will be sent to this language.
Production Hint: set fallback language to some meaningful language, eg. 'en'
options.fallbackLang = nil;
As the fallbackLang will default to 'dev' you can turn it off by setting the option value to nil. This will prevent loading the fallbacks resource file and any futher look up of missing value inside a fallback file.
// given resourcesfile namespace1.en.json (default ns)
{
key1: 'value of key 1'
}
// given additional resourcesfile namespace2.en.json
{
keys: {
2: 'value of key 2',
3: 'value of key 3'
}
}
options.fallbackToDefaultNamespace = YES;
...
I18NEXT(@"namespace2:key1"); // -> value of key 1
// given resourcesfile namespace1.en.json
{
key1: 'value of key 1 - ns1'
}
// given resourcesfile namespace2.en.json
{
key1: 'value of key 1 - ns2'
key2: 'value of key 2 - ns2'
}
// given resourcesfile namespace3.en.json
{
keys: {
2: 'value of key 2',
3: 'value of key 3'
}
}
options.fallbackNamespace = @"namespace2";
...
I18NEXT("namespace3:key1"); // -> value of key 1 - ns2
// array
options.fallbackNamespaces = @[ @"namespace1", @"namespace2" ]; // order matters
...
I18NEXT("namespace3:key1"); // -> value of key 1 - ns1
I18NEXT("namespace3:key2"); // -> value of key 2 - ns2
If a resource can't be found in namespace it will be looked up in default namespace. By default this option is turned off.
I18NextOptions* options = [I18NextOptions new];
options.langLoadType = I18NextLangLoadTypeCurrent;
If langLoadType option is set to current i18next will load the current set language (this could be a specific (en-US) or unspecific (en) resource file).
Hint: to prevent loading the fallbackLang's resource file set fallbackLang
to nil
.
I18NextOptions* options = [I18NextOptions new];
options.langLoadType = I18NextLangLoadTypeUnspecific;
If set to unspecific i18next will always load the unspecific resource file (eg. en instead of en-US).
Hint: to prevent loading the fallbackLang's resource file set fallbackLang
to nil
.
// given resourcesfile namespace1.dev.json (fallback lang)
{
key1: 'fallback'
}
// given resourcesfile namespace1.en.json
{
key1: null
}
options.fallbackOnNull = YES;
...
I18NEXT("key1"); // -> 'fallback'
options.fallbackOnNull = NO;
...
I18NEXT("key1"); // -> ''
Default is YES.
// given resourcesfile namespace1.dev.json (fallback lang)
{
key1: 'fallback'
}
// given resourcesfile namespace1.en.json
{
key1: ''
}
This feature is not implemented in the iOS library
I18NextOptions* options = [I18NextOptions new];
// tree: lng -> namespace -> key -> nested key
options.resourcesStore = @{
@"dev": @{ @"translation": @{ @"key": @"value" } },
@"en": @{ @"translation": @{ @"key": @"value" } },
@"en-US": @{ @"translation": @{ @"key": @"value" } },
};
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
As you provide the resources, the completion block will fire immediatly and no external resources will be loaded!
I18NextOptions* options = [I18NextOptions new];
options.resourcesBaseURL = [NSURL URLWithString:@"http://example.com"];
options.resourcesGetPathTemplate = @"locales/__lng__/__ns__.json";
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
Will load 'http://example.com/locales/en-US/translation.json'.
If language is set to 'en-US' following resource files will be loaded one-by-one:
- en-US
- en
- dev (default fallback language)
Hints:
- to keep the fetched resources in the local cache, set option
updateLocalCache = YES
- to lowercase countryCode in requests, eg. to 'en-us', set option
lowercaseLang = YES
I18NextOptions* options = [I18NextOptions new];
options.resourcesBaseURL = [NSURL URLWithString:@"http://example.com"];
options.resourcesGetPathTemplate = @"resources.json?lng=__lng__&ns=__ns__";
options.dynamicLoad = YES;
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
Will request 'http://example.com/resources.json?lng=en-US+en+dev&ns=translation'.
If language is set to 'en-US' following resources will be loaded in one request:
- en-US
- en
- dev (default fallback language)
Hint: to keep the fetched resources in the local cache, set option updateLocalCache = YES
I18NextOptions* options = [I18NextOptions new];
options.loadFromLanguageBundles = YES;
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
Will load resources from the app bundle lproj folders: en.lproj/i18next.json
This can be combined with the loadFromLocalCache
option.
In that case the local cache will be loaded first
and the language bundles will only be used if the cache is empty.
Hint: to use another filename, eg. 'myFile' set option filenameInLanguageBundles = @"myFile"
I18NextOptions* options = [I18NextOptions new];
options.loadFromLocalCache = YES;
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
Will load resources from the local cache.
The local cache is updated whenever you load new resource from a remote server while updateLocalCache = YES
is set.
Hint: to use a custom cache path, set the localCachePath
option.
I18NextOptions* options = [I18NextOptions new];
options.loadFromLanguageBundles = YES;
options.loadFromLocalCache = YES;
options.synchronousLocalLoad = YES;
[i18n loadWithOptions:[options asDictionary] completion:^(NSError *error) {
// new resources are successfully loaded if error == nil
}];
I18NEXT("key"); // -> "value"
Will load local resources synchronously (only for language bundles or the local cache). This is useful at app initialization so the next call to translate a key can succeed (if it exists in the language bundle or local cache).
Not all features of the javascript implementation of i18next are currently supported. Here is the list from the i18next website and how to use them with this library.
// given resourcefile translation.en.json
{
key1: 'value of key 1'
}
I18NEXT(@"key1"); // -> value of key 1
// given resourcesfile namespace1.en.json (default ns)
{
key1: 'value of key 1'
}
// given additional resourcesfile namespace2.en.json
{
keys: {
2: 'value of key 2',
3: 'value of key 3'
}
}
I18NEXT(@"key1"); // -> value of key 1
I18NEXT(@"namespace1.key1"); // -> value of key 1
I18NEXT(@"keys.2"); // -> missing key
I18NEXT(@"namespace2:keys.2"); // -> value of key 2
I18NEXT(@"namespace2:keys.3"); // -> value of key 3
// given resourcefile translation.en.json
{
key1: 'value of key 1'
}
I18NEXT(@[ @"notExists", @"key1" ]); // -> value of key 1
// given resources in arabic
{
'en-US': {
translation: {
key: [
"line1",
"line2",
"line3"
]
}
}
};
The translation will be joined with '\n'.
// given resources in arabic
{
'en-US': {
translation: {
people: [
{ name: "tom" },
{ name: "steve" }
]
}
}
};
This feature is not implemented in the iOS library
// given resources
{
'en-US': { translation: { // key not found } }
};
To get a default value if not found:
I18NEXT(@"key" defaultValue:@"my text"); // -> my text
// given resources
{
dev: { translation: { nesting1: '1 $t(nesting2)' } },
en: { translation: { nesting2: '2 $t(nesting3)' } },
'en-US': { translation: { nesting3: '3' } }
};
I18NEXT(@"nesting1"); // -> 1 2 3
// given resources
{
en: { translation: {
girlsAndBoys: '$t(girls, {"count": __girls__}) and __count__ boy',
girlsAndBoys_plural: '$t(girls, {"count": __girls__}) and __count__ boys' },
girls: '__count__ girl',
girls_plural: '__count__ girls' } }
};
I18NEXT(@"girlsAndBoys" count:2 variables:@{ @"girls": @"3"}); // -> 3 girls and 2 boys
// given resources
{
'en-US': { translation: { key: '__myVar__ are important' } }
};
I18NEXT(@"key" variables:@{ @"myVar": @"variables"}); // -> variables are important
// given resources
{
'en-US': { translation: {
key1: 'The first 4 letters of the english alphabet are: %s, %s, %s and %s'
}}
};
[[I18Next sharedInstance] tf:@"key1", "a", "b", "c", "d"];
// given resources
{
'en-US': {
translation: {
key: '__count__ child',
key_plural: '__count__ children'
}
}
};
I18NEXT(@"key" count:0); // -> 0 children
I18NEXT(@"key" count:1); // -> 1 child
I18NEXT(@"key" count:5); // -> 5 children
// given resources
{
'en-US': {
translation: {
key: '__count__ child',
key_plural: '__count__ children',
key_indefinite: 'a child',
key_plural_indefinite: 'some children'
}
}
};
This feature is not implemented in the iOS library
// given resources in arabic
{
'ar': {
translation: {
key: 'singular',
key_plural_0: 'zero',
key_plural_2: 'two',
key_plural_3: 'few',
key_plural_11: 'many',
key_plural_100: 'plural'
}
}
};
I18NEXT(@"key" count: 0); // -> zero
I18NEXT(@"key" count: 1); // -> singular
I18NEXT(@"key" count: 2); // -> two
I18NEXT(@"key" count: 3); // -> few
I18NEXT(@"key" count: 4); // -> few
I18NEXT(@"key" count: 104); // -> few
I18NEXT(@"key" count: 11); // -> many
I18NEXT(@"key" count: 99); // -> many
I18NEXT(@"key" count: 199); // -> many
I18NEXT(@"key" count: 100); // -> plural
What did you expect ;).
Hint: i18next provides the functionality for all languages.
// given resources
{
'en-US': {
translation: {
friend: 'A friend',
friend_male: 'A boyfriend',
friend_female: 'A girlfriend'
}
}
};
I18NEXT(@"friend" count:0); // -> A friend
I18NEXT(@"friend" context:@"male"); // -> A boyfriend
I18NEXT(@"friend" context:@"female"); // -> A girlfriend
i18next and all its contributors.