Skip to content

Commit 91c6226

Browse files
author
Olaf Kwant
committed
i18n changes
1 parent fdd9cdb commit 91c6226

File tree

4 files changed

+104
-47
lines changed

4 files changed

+104
-47
lines changed

doc/i18n.md

+18-21
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
## Using the I18n module
22

3-
The I18n module provides a helper called `I18n.t` that you can use to localize
4-
your application.
3+
The I18n module provides a helper called `i18n.t` that you can use to localize your application.
54

65
### Setup
76

8-
Put a `defaultLocale` property into your `manifest.json` file.
7+
Put a `defaultLocale` property into your `manifest.json` file, otherwise it will use english (en)
98

10-
Add your translation files as `src/translations/XX.json` where `XX` is a locale such
11-
as `en-US`, `de`, or `zh`.
9+
Add your translation files as `src/translations/XX.json` where `XX` is a locale such as `en-US`, `de`, or `zh`.
1210

1311
A simple translation file might look like this:
1412

@@ -22,40 +20,39 @@ A simple translation file might look like this:
2220
}
2321
```
2422

23+
The "app" section in translation files is used *only* for public app listings in the Zendesk Marketplace. For these listings, we only allow English. The "app" sections in other translation files will be ignored.
24+
2525
### Initialization
2626

27-
When you know which locale you want to use, call `I18n.loadTranslations(locale)` where
28-
`locale` is a string like `en-US`, `de`, `zh`. This will load the strings under the
29-
matching file in `src/translations`. For example, you could use
27+
When you know which locale you want to use, call `i18n.loadTranslations(locale)` where `locale` is a string like `en-US`, `de`, `zh`. This will load the strings under the matching file in `src/translations`. For example, you could use
3028

3129
```javascript
32-
import I18n from 'i18n';
30+
import i18n from 'i18n';
3331

3432
const zafClient = ZAFClient.init();
35-
zafClient.get('currentUser.locale').then(function(data) {
36-
I18n.loadTranslations(data['currentUser.locale']);
33+
zafClient.get('currentUser.locale').then((data) => {
34+
const locale = data['currentUser.locale'];
35+
i18n.loadTranslations(locale);
3736
});
3837
```
3938

4039
## Reference
4140

42-
### I18n.loadTranslations(locale)
41+
### i18n.loadTranslations(locale)
4342

44-
Sets the locale to be used by `I18n.t()`
43+
Sets the locale to be used by `i18n.t()`
4544

4645
#### Arguments
4746

4847
* `locale`: String representing the filename of the required translation JSON file.
4948

50-
### I18n.t(key, context)
49+
### i18n.t(key, context)
5150

52-
Returns a translated string using a key that is available in the relevant
53-
translation file (found in `src/translations/XX.json`).
51+
Returns a translated string using a key that is available in the relevant translation file (found in `src/translations/XX.json`).
5452

5553
#### Arguments
5654

57-
* `key`: The key assigned to the string in the JSON file. Dots may be used to access
58-
nested objects.
55+
* `key`: The key assigned to the string in the JSON file. Dots may be used to access nested objects.
5956
* `context`: An object with named values to be interpolated into the resulting string.
6057

6158
#### Returns
@@ -65,7 +62,7 @@ A translated string generated by keying into the JSON file and interpolating any
6562
#### Example
6663

6764
```javascript
68-
I18n.t('hello'); // returns "Hello!"
69-
I18n.t('goodbye', { name: 'Yak' }); // returns "Bye Yak!"
70-
I18n.t('formal.farewell'); // returns "Farewell, friend."
65+
i18n.t('hello'); // returns "Hello!"
66+
i18n.t('goodbye', { name: 'Yak' }); // returns "Bye Yak!"
67+
i18n.t('formal.farewell'); // returns "Farewell, friend."
7168
```

spec/i18n.spec.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint-env jest */
2+
import i18n from '../src/javascripts/lib/i18n'
3+
4+
const mock_en = {
5+
'one': 'the first translation',
6+
'two.one': 'the second for: {{name}}',
7+
'two.two': 'the second for: {{name}}-{{other}}',
8+
'three.one.one': 'the {{name}} for {{name}} should be {{name}}'
9+
}
10+
11+
jest.mock('../src/translations/en', () => {
12+
return mock_en
13+
})
14+
15+
jest.mock('../src/translations/fr', () => {
16+
throw new Error('no such file')
17+
})
18+
19+
describe('i18n', () => {
20+
beforeAll(() => {
21+
i18n.loadTranslations('en')
22+
})
23+
24+
describe('#tryRequire', () => {
25+
it('returns a json if the file exists', () => {
26+
const result = i18n.tryRequire('en')
27+
expect(result).toBe(mock_en)
28+
})
29+
30+
it('returns null if the file doesn\'t exist', () => {
31+
const result = i18n.tryRequire('fr')
32+
expect(result).toBe(null)
33+
})
34+
})
35+
36+
describe('#t', () => {
37+
it('returns a string', () => {
38+
const result = i18n.t('one')
39+
expect(result).toBe('the first translation')
40+
})
41+
42+
it('interpolates one string', () => {
43+
const result = i18n.t('two.one', {
44+
name: 'olaf'
45+
})
46+
expect(result).toBe('the second for: olaf')
47+
})
48+
49+
it('interpolates multiple strings', () => {
50+
const result = i18n.t('two.two', {
51+
name: 'olaf',
52+
other: 'test'
53+
})
54+
expect(result).toBe('the second for: olaf-test')
55+
})
56+
57+
it('interpolates duplicates strings', () => {
58+
const result = i18n.t('three.one.one', {
59+
name: 'olaf'
60+
})
61+
expect(result).toBe('the olaf for olaf should be olaf')
62+
})
63+
})
64+
})

src/javascripts/lib/i18n.js

+19-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import manifest from '../../manifest.json'
22

3-
const defaultLocale = manifest.defaultLocale || 'en'
4-
53
// map to store the key/translation pairs of the loaded language
64
let translations
75

@@ -21,25 +19,25 @@ function parsePlaceholders (str, context) {
2119
if (match) matches.push(match)
2220
} while (match)
2321

24-
return matches.reduce(
25-
(str, match) => {
26-
const newRegex = new RegExp(match[0], 'g')
27-
str = str.replace(newRegex, context[match[1]])
28-
29-
return str
30-
},
31-
str
32-
)
22+
return matches.reduce((str, match) => {
23+
const newRegex = new RegExp(match[0], 'g')
24+
str = str.replace(newRegex, context[match[1]])
25+
return str
26+
}, str)
3327
}
3428

35-
const I18n = {
36-
tryRequire: function (locale) {
29+
class I18n {
30+
constructor (locale = 'en') {
31+
this.loadTranslations(locale)
32+
}
33+
34+
tryRequire (locale) {
3735
try {
3836
return require(`../../translations/${locale}.json`)
3937
} catch (e) {
4038
return null
4139
}
42-
},
40+
}
4341

4442
/**
4543
* Translate key with currently loaded translations,
@@ -48,8 +46,8 @@ const I18n = {
4846
* @param {Object} context object contains placeholder/value pairs
4947
* @return {String} tranlated string
5048
*/
51-
t: function (key, context) {
52-
if (!translations) throw new Error('Translations must be initialized with I18n.loadTranslations before calling `t`.')
49+
t (key, context) {
50+
if (!translations) throw new Error('Translations must be initialized with i18n.loadTranslations before calling `t`.')
5351

5452
const keyType = typeof key
5553
if (keyType !== 'string') throw new Error(`Translation key must be a string, got: ${keyType}`)
@@ -59,16 +57,12 @@ const I18n = {
5957
if (typeof template !== 'string') throw new Error(`Invalid translation for key: ${key}`)
6058

6159
return parsePlaceholders(template, context)
62-
},
63-
64-
loadTranslations: function (locale) {
65-
translations = I18n.tryRequire(locale) ||
66-
I18n.tryRequire(locale.replace(/-.+$/, '')) || // e.g. fallback `en-US` to `en`
67-
I18n.tryRequire(defaultLocale) ||
68-
{}
60+
}
6961

70-
return translations
62+
loadTranslations (locale) {
63+
const newTranslations = this.tryRequire(locale) || this.tryRequire(locale.replace(/-.+$/, ''))
64+
if (newTranslations) translations = newTranslations
7165
}
7266
}
7367

74-
export default I18n
68+
export default new I18n(manifest.defaultLocale)

src/manifest.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"private": true,
1010
"location": {
1111
"support": {
12-
"ticket_sidebar": "assets/iframe.html"
12+
"ticket_sidebar": {
13+
"url": "assets/iframe.html"
14+
}
1315
}
1416
},
1517
"version": "1.0",

0 commit comments

Comments
 (0)