Skip to content

Commit

Permalink
Add ability to tag context to translations so duplicate keys can still
Browse files Browse the repository at this point in the history
have different meanings
  • Loading branch information
zakkudo committed Sep 23, 2018
1 parent acd8955 commit 8adf4b2
Show file tree
Hide file tree
Showing 15 changed files with 786 additions and 590 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ A library for scanning javscript files to build translation mappings in json aut
## Why use this?

- You no longer have to manage hierarchies of translations
- Templates are automatically generated for the translators
- The translations are noted if they are new, unused and what files
- It allows splitting the translations easily for dynamic imports to allow sliced loading
- Any string wrapped in `__()` or `__n()`, will be picked up as a
- Designed for architectures leveraging dynamic imports, allowing splitting of the translations based off of file structure
- Templates are automatically generated for the translators where they only need to fill in the blanks
- The translations are annoted if they are new or unused as well as the file names and line numbers of usages
- Easy auditing for missing or non-updated translation strings with never running your application or enlisting QA
- Any string wrapped in `__()` or `__n()` or `__p()` or `__np()`, will be picked up as a
translatable making usage extremely easy for developers
- Works similarly to the venerable [gettext](https://en.wikipedia.org/wiki/Gettext). Any translation strategies that work for that library work for this library.

## What does it do?

Expand All @@ -36,7 +38,7 @@ yarn add @zakkudo/translation-static-analyzer
```

## Setup
1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` using a library like `@zakkudo/translator`
1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` or `__p('context', 'text')` or `__np('context', 'singular', 'plural', number)`` using a library like `@zakkudo/translator`
2. Initialize the analyzer in your build scripts similar to below.
``` javascript
const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer');
Expand Down Expand Up @@ -88,17 +90,28 @@ Where `locales/fr.json` will look like this for use by your translators:
// src/pages/AboutPage/index.js:14
"About": "",
// UNUSED
"Search Page": "French translation",
"Search": {
"default": "French translation",
"menuitem": "French translation"
},
// src/pages/AboutPage/index.js:40
"There is one user": {"one":"French translation", "other":"French translation"},
"There is one user": {
"default": {"one":"French translation", "other":"French translation"},
},
// src/pages/AboutPage/index.js:38
"Welcome to the about page!": "French translation"
"Welcome to the about page!": {
"default": "French translation"
}
}
```

And the optimized `src/.locales/fr.json` will look like this for use by your developers:
``` json
{
"Search": {
"default": "French translation",
"menuitem": "French translation"
},
"There is one user": {"one":"French translation", "other":"French translation"},
"Welcome to the about page!": "French translation"
}
Expand Down
18 changes: 13 additions & 5 deletions __mocks__/filesystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ export default class SearchPage extends Component {
}
static get template() {
return '{{__('invalid''string')}} <div>{{__n('%d result', '%d results', 2)}}</div>';
return '{{__('invalid''string')}} {{__p('menuitem', 'Search')}} <div>{{__n('%d result', '%d results', 2)}}</div> {{__np('footer', '%d view', '%d views', 23)}}';
}
};
`.trim();
Expand All @@ -34,16 +33,25 @@ export default class Application extends Component {
};
`.trim();

const EmptyKeysPage = `
export default class Application extends Component {
static get title() {
return __('') + __n('') + __np('') + __p('');
}
};
`.trim();

module.exports = {
'src/pages/Search/index.js': SearchPage,
'src/pages/About/index.js': AboutPage,
'src/pages/Empty/index.js': EmptyPage,
'src/pages/EmptyKeys/index.js': EmptyKeysPage,
'src/test.js': EmptyPage,
'src/index.js': Application,
'./locales/existing.json': JSON.stringify({
"Search": "検索",
"test unused key": "test value",
"Application": "アプリケーション",
"Search": {"default": "検索"},
"test unused key": {"default": "test value"},
"Application": {"default": "アプリケーション"},
}),
'src/pages/About/.locales/existing.json': JSON.stringify({}),
'src/pages/Search/.locales/existing.json': JSON.stringify({'Search': ''}),
Expand Down
4 changes: 4 additions & 0 deletions __mocks__/glob.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ glob.sync.mockImplementation((pattern) => {
'src/pages/Search',
'src/pages/About',
];
} else if (pattern === 'test empty keys') {
return [
'src/pages/EmptyKeys/index.js',
];
}

return [];
Expand Down
41 changes: 0 additions & 41 deletions __mocks__/y18n.js

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.17",
"description": "A library for generating localization files using static analysis of source files similar to gettext",
"keywords": [
"gettext",
"i18n",
"internationalization",
"l10n",
Expand Down Expand Up @@ -43,8 +44,7 @@
"fs-extra": "^7.0.0",
"glob": "^7.1.2",
"json5": "^1.0.1",
"safe-eval": "^0.4.1",
"y18n": "^4.0.0"
"safe-eval": "^0.4.1"
},
"scripts": {
"build": "scripts/build.sh",
Expand Down
29 changes: 21 additions & 8 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ A library for scanning javscript files to build translation mappings in json aut
## Why use this?

- You no longer have to manage hierarchies of translations
- Templates are automatically generated for the translators
- The translations are noted if they are new, unused and what files
- It allows splitting the translations easily for dynamic imports to allow sliced loading
- Any string wrapped in `__()` or `__n()`, will be picked up as a
- Designed for architectures leveraging dynamic imports, allowing splitting of the translations based off of file structure
- Templates are automatically generated for the translators where they only need to fill in the blanks
- The translations are annoted if they are new or unused as well as the file names and line numbers of usages
- Easy auditing for missing or non-updated translation strings with never running your application or enlisting QA
- Any string wrapped in `__()` or `__n()` or `__p()` or `__np()`, will be picked up as a
translatable making usage extremely easy for developers
- Works similarly to the venerable [gettext](https://en.wikipedia.org/wiki/Gettext). Any translation strategies that work for that library work for this library.

## What does it do?

Expand All @@ -36,7 +38,7 @@ yarn add @zakkudo/translation-static-analyzer
```

## Setup
1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` using a library like `@zakkudo/translator`
1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` or `__p('context', 'text')` or `__np('context', 'singular', 'plural', number)`` using a library like `@zakkudo/translator`
2. Initialize the analyzer in your build scripts similar to below.
``` javascript
const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer');
Expand Down Expand Up @@ -88,17 +90,28 @@ Where `locales/fr.json` will look like this for use by your translators:
// src/pages/AboutPage/index.js:14
"About": "",
// UNUSED
"Search Page": "French translation",
"Search": {
"default": "French translation",
"menuitem": "French translation"
},
// src/pages/AboutPage/index.js:40
"There is one user": {"one":"French translation", "other":"French translation"},
"There is one user": {
"default": {"one":"French translation", "other":"French translation"},
},
// src/pages/AboutPage/index.js:38
"Welcome to the about page!": "French translation"
"Welcome to the about page!": {
"default": "French translation"
}
}
```

And the optimized `src/.locales/fr.json` will look like this for use by your developers:
``` json
{
"Search": {
"default": "French translation",
"menuitem": "French translation"
},
"There is one user": {"one":"French translation", "other":"French translation"},
"Welcome to the about page!": "French translation"
}
Expand Down
40 changes: 23 additions & 17 deletions src/hasTranslation.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
/**
* @private
*/
function singularHasTranslation(data) {
return Boolean(data.length);
}

/**
* @private
*/
function pluralHasTranslation(data) {
return Object.values(data || {}).some((v) => {
if (Object(v) === v) {
return pluralHasTranslation(v);
}

return singularHasTranslation(v);
});
}

/**
* Checks of a translation key-value pair has been created.
* @param {*} data - An object that is spidered looking for
Expand All @@ -6,23 +26,9 @@
* @private
*/
module.exports = function hasTranslation(data) {
if (Object(data) === data) {
const keys = Object.keys(data);

if (!keys.length) {
return false;
}

return keys.some((k) => {
if (typeof data[k] === 'string') {
return Boolean(data[k].length);
} else {
return hasTranslation(data[k]);
}
});
} else if (typeof data === 'string') {
return Boolean(data.length);
if (typeof data === 'string') {
return singularHasTranslation(data);
}

return false;
return pluralHasTranslation(data);
}

0 comments on commit 8adf4b2

Please sign in to comment.