Skip to content
This repository has been archived by the owner on Oct 1, 2024. It is now read-only.

Commit

Permalink
feat(translate): detect keys of i18n.translate() function
Browse files Browse the repository at this point in the history
  • Loading branch information
skolmer committed Jul 4, 2018
1 parent 744f078 commit e6dc800
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 18 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* [⚠️ Known Limitations](#%EF%B8%8F-known-limitations)
* [📦 Installation](#-installation)
* [💻 Examples](#-examples)
* [🎩 Key detection for i18n.translate()](#-key-detection-for-i18ntranslate)
* [📒 Usage](#-usage)
+ [Via npm](#via-npm)
- [package.json](#packagejson)
Expand Down Expand Up @@ -46,6 +47,7 @@ i18n-tag-schema can also be used to export translation keys into a simple json f

This module does include a JSON validator that helps you keep track of missing or invalid keys in your translation files and shows you the current translation coverage of your project. A translation file is considered valid if it covers 100% of the translation keys defined in the JSON schema. This feature can be integrated into an automated build pipeline to check the translation coverage of a build. It can also be used to write unit tests that fail if your modules are not fully translated.


## ⚠️ Known Limitations

You cannot use variables for grouping.
Expand Down Expand Up @@ -83,6 +85,17 @@ $ npm install i18n-tag-schema --save-dev
* [npm scripts](https://github.com/skolmer/i18n-tag-examples/tree/master/ReactJS)
* [gulp](https://github.com/skolmer/i18n-tag-examples/tree/master/Simple)


## 🎩 Key detection for i18n.translate()

If you are using `i18n.translate()` to translate variables you can add comments telling i18n-tag-schema the possible values of your variables.
The comment should be a `string` or `string[]` in valid JSON syntax.

```js
i18n.translate(myVariable1 /* "possible value ${0}" */, 'expression value')
i18n.translate(myVariable2 /* ["possible value 1", "another value"] */)
```

## 📒 Usage
```js
import { generateTranslationSchema } from 'i18n-tag-schema'
Expand Down
63 changes: 62 additions & 1 deletion __tests__/data/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})"
},
"Hello ${0}, you have ${1} in your bank account.": {
"type": "string",
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})"
},
"Total: ${0}": {
"type": "string",
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})"
},
"components/Clock.js": {
"type": "object",
"properties": {
"Time": {
"type": "string",
"minLength": 1
}
},
"required": [
"Time"
]
},
"custom group": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -90,16 +112,55 @@
"Hello ${0}, you have ${1} in your bank account.",
"Hello!"
]
},
"test ${0}": {
"type": "string",
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})"
},
"translate/translate.js": {
"type": "object",
"properties": {
"Time: ${0}": {
"type": "string",
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})"
},
"Welcome": {
"type": "string",
"minLength": 1
},
"myvar1": {
"type": "string",
"minLength": 1
},
"myvar2 ${0} ${1}": {
"type": "string",
"minLength": 1,
"pattern": "(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})"
}
},
"required": [
"Time: ${0}",
"Welcome",
"myvar1",
"myvar2 ${0} ${1}"
]
}
},
"required": [
"\n <user name=\"${0}\">${1}</user>\n ",
"\n <users>\n ${0}\n </users>\n",
"${0} ${1}",
"Hello ${0}, you have ${1} in your bank account.",
"Total: ${0}",
"components/Clock.js",
"custom group",
"custom group 2",
"custom inline group",
"grouped.js"
"grouped.js",
"test ${0}",
"translate/translate.js"
],
"additionalProperties": false
}
28 changes: 28 additions & 0 deletions __tests__/data/translate/translate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

const name = 'Steffen'
const amount = 1250.33
const x = 'test'
i18n.translate(x /* "test ${0}" */, name)

i18n.translate('Hello ${0}, you have ${1} in your bank account.', name, { value: amount, formatter: 'c'})

i18n.translate('Total: ${0}', { value: amount, formatter: 'd', format: 2})

i18n(__translationGroup, 'my-lib').translate('Welcome') // Select translation from module group e.g. "components/App.js"
i18n('components/Clock.js', 'my-lib').translate('Time') // Select translation from a custom group
i18n(__translationGroup, 'my-lib').translate(x /* ["myvar1", "myvar2 ${0} ${1}"] */)

class Clock {
tick() {
return this.i18n.translate('Time: ${0}', { value: new Date(), formatter: 't', format: 'T' })
}
}
export default i18nGroup(__translationGroup, 'my-lib')(Clock)

@i18nGroup(null, 'my-lib')
class Test {
tick() {
return this.i18n.translate('Total: ${0}', { value: 45.9956, formatter: 'n', format: '2' })
}
}
export { Test }
12 changes: 12 additions & 0 deletions __tests__/data/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,17 @@
"custom inline group": {
"Hallo!": "Hello!",
"Hello!": "Hallo!"
},
"components/Clock.js": {
"Time": "Time"
},
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"test ${0}": "test ${0}",
"Total: ${0}": "Total: ${0}",
"translate/translate.js": {
"Time: ${0}": "Time: ${0}",
"Welcome": "Welcome",
"myvar1": "test",
"myvar2 ${0} ${1}": "test ${0} ${1}"
}
}
12 changes: 12 additions & 0 deletions __tests__/data/translation.valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,17 @@
"grouped.js": {
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"Hello!": "Hello!"
},
"components/Clock.js": {
"Time": "Time"
},
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"test ${0}": "test ${0}",
"Total: ${0}": "Total: ${0}",
"translate/translate.js": {
"Time: ${0}": "Time: ${0}",
"Welcome": "Welcome",
"myvar1": "test",
"myvar2 ${0} ${1}": "test ${0} ${1}"
}
}
12 changes: 12 additions & 0 deletions __tests__/data/translations/translation.valid.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,17 @@
"grouped.js": {
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"Hello!": "Hello!"
},
"components/Clock.js": {
"Time": "Time"
},
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"test ${0}": "test ${0}",
"Total: ${0}": "Total: ${0}",
"translate/translate.js": {
"Time: ${0}": "Time: ${0}",
"Welcome": "Welcome",
"myvar1": "test",
"myvar2 ${0} ${1}": "test ${0} ${1}"
}
}
12 changes: 12 additions & 0 deletions __tests__/data/translations/translation.valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,17 @@
"grouped.js": {
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"Hello!": "Hello!"
},
"components/Clock.js": {
"Time": "Time"
},
"Hello ${0}, you have ${1} in your bank account.": "Hello ${0}, you have ${1} in your bank account.",
"test ${0}": "test ${0}",
"Total: ${0}": "Total: ${0}",
"translate/translate.js": {
"Time: ${0}": "Time: ${0}",
"Welcome": "Welcome",
"myvar1": "test",
"myvar2 ${0} ${1}": "test ${0} ${1}"
}
}
20 changes: 19 additions & 1 deletion __tests__/exportTranslationKeys.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ describe('exportTranslationKeys', () => {
'\n <user name="${0}">${1}</user>\n ',
'\n <users>\n ${0}\n </users>\n',
'${0} ${1}',
'Hello ${0}, you have ${1} in your bank account.',
'Total: ${0}',
{
'group': 'components/Clock.js',
'items': [
'Time',
],
},
{
'group': 'custom group',
'items': [
Expand Down Expand Up @@ -85,6 +93,16 @@ describe('exportTranslationKeys', () => {
'Hello ${0}, you have ${1} in your bank account.',
'Hello!'
]
},
'test ${0}',
{
'group': 'translate/translate.js',
'items': [
'Time: ${0}',
'Welcome',
'myvar1',
'myvar2 ${0} ${1}',
]
}
])
})
Expand All @@ -103,7 +121,7 @@ describe('exportTranslationKeys', () => {
totalCount = total
}
})
expect(totalCount).toEqual(7)
expect(totalCount).toEqual(8)
})

it('should export grouped templates as array', async () => {
Expand Down
65 changes: 63 additions & 2 deletions __tests__/generateTranslationSchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ const expected = {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})'
},
'Hello ${0}, you have ${1} in your bank account.': {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})',
'type': 'string',
},
'Total: ${0}': {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})',
'type': 'string',
},
'components/Clock.js': {
'properties': {
'Time': {
'minLength': 1,
'type': 'string',
},
},
'required': [
'Time',
],
'type': 'object',
},
'custom group': {
'type': 'object',
'properties': {
Expand Down Expand Up @@ -97,16 +119,55 @@ const expected = {
'Hello ${0}, you have ${1} in your bank account.',
'Hello!'
]
},
'test ${0}': {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})',
'type': 'string',
},
'translate/translate.js': {
'properties': {
'Time: ${0}': {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})',
'type': 'string',
},
'Welcome': {
'minLength': 1,
'type': 'string',
},
'myvar1': {
'minLength': 1,
'type': 'string',
},
'myvar2 ${0} ${1}': {
'minLength': 1,
'pattern': '(?=.*?\\$\\{0\\})(?=.*?\\$\\{1\\})',
'type': 'string',
},
},
'required': [
'Time: ${0}',
'Welcome',
'myvar1',
'myvar2 ${0} ${1}',
],
'type': 'object',
}
},
'required': [
'\n <user name="${0}">${1}</user>\n ',
'\n <users>\n ${0}\n </users>\n',
'${0} ${1}',
'Hello ${0}, you have ${1} in your bank account.',
'Total: ${0}',
'components/Clock.js',
'custom group',
'custom group 2',
'custom inline group',
'grouped.js'
'grouped.js',
'test ${0}',
'translate/translate.js',
],
'additionalProperties': false
}
Expand Down Expand Up @@ -148,7 +209,7 @@ describe('i18n-tag-schema', () => {
expect(name).toBeDefined()
expect(current > last || current === total).toBeTruthy()
last = current
expect(total).toEqual(7)
expect(total).toEqual(8)
}
})
})
Expand Down
4 changes: 2 additions & 2 deletions __tests__/validateTranslations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('validateTranslations', () => {
logger: { toConsole: true }
})
} catch(err) {
expect(err.message).toContain('translation.json has 4 missing translations and 1 invalid key; 64% translated.')
expect(err.message).toContain('translation.json has 4 missing translations and 1 invalid key; 79% translated.')
expect(err.message).toContain('translation.valid.json is valid and 100% translated!')
}
})
Expand All @@ -122,7 +122,7 @@ describe('validateTranslations', () => {
logger: { toConsole: true }
})
} catch(err) {
expect(err.message).toEqual('translation.json has 4 missing translations and 1 invalid key; 64% translated.')
expect(err.message).toEqual('translation.json has 4 missing translations and 1 invalid key; 79% translated.')
}
})

Expand Down
3 changes: 2 additions & 1 deletion jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules"
Expand Down
Loading

0 comments on commit e6dc800

Please sign in to comment.