Skip to content

Commit 87dc51d

Browse files
author
Gertjan Reynaert
committed
Add first files WIP
1 parent ed9f9c9 commit 87dc51d

File tree

13 files changed

+468
-0
lines changed

13 files changed

+468
-0
lines changed

dist/.keep

Whitespace-only changes.

src/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# React-intl-translations-manager
2+
3+
## Installing
4+
5+
```
6+
npm install --save-dev react-intl-translations-manager
7+
```
8+
9+
## Usage
10+
11+
Since you need the `babel-plugin-react-intl` to extract the messages, I'll assume you're using babel in your project.
12+
13+
This is an example of the most basic usage of this plugin, in the API documentation below you can find more options.
14+
15+
Create a script in your package.json
16+
```json
17+
{
18+
"scripts": {
19+
"manage:translations": "babel-node ./translationRunner.js"
20+
}
21+
}
22+
```
23+
Create a file with your config you can run with the npm script
24+
```js
25+
// translationRunner.js
26+
import { createTranslationManager } from 'react-intl-translations-manager';
27+
28+
const translationManager = createTranslationManager({
29+
messagesDirectory: 'src/translations/extractedMessages',
30+
translationsDirectory: 'src/translations/locales/',
31+
languages: ['nl'], // any language you need
32+
});
33+
34+
translationManager.run();
35+
```
36+
37+
Run the translation manager with your new npm script
38+
39+
```
40+
npm run manage:translations
41+
```
42+
43+
## API
44+
45+
### createTranslationManager
46+
47+
This will create a new translationManager based on the config you gave in. On
48+
itself this won't do anything unless you run any of the commands below.
49+
50+
#### Config
51+
52+
- `messagesDirectory` (required),
53+
- Directory where the babel plugin puts the extracted messages. This path is
54+
relative to your projects root.
55+
- example: `src/locales/extractedMessages`
56+
- `translationsDirectory` (required),
57+
- Directory of the translation files the translation manager needs to maintain.
58+
- example: `src/locales/lang`
59+
- `singleMessagesFile` (optional, default: `false`)
60+
- Option to output a single file containing the aggregate of all extracted messages,
61+
grouped by the file they were extracted from.
62+
- example:
63+
```json
64+
[
65+
{
66+
"path": "src/components/foo.json",
67+
"descriptors": [
68+
{
69+
"id": "bar",
70+
"description": "Text for bar",
71+
"defaultMessage": "Bar",
72+
}
73+
]
74+
}
75+
]
76+
```
77+
- `whitelistsDirectory` (optional, default: `translationsDirectory`)
78+
- Directory of the whitelist files the translation manager needs to maintain.
79+
These files contain the key of translations that have the exact same text in a specific language as the defaultMessage. Specifying this key will suppress
80+
`unmaintained translation` warnings.
81+
- example: `Dashboard` in english is also accepted as a valid translation for
82+
dutch.
83+
- `languages` (optional, default: `[]`)
84+
- What languages the translation manager needs to maintain. Specifying no
85+
languages actually doesn't make sense, but won't break the translationManager
86+
either.
87+
- example: for `['nl', 'fr']` the translation manager will maintain a `nl.json`, `fr.json`, `whitelist_nl.json` and a `whitelist_fr.json` file
88+
- `detectDuplicateIds` (optional, default: `true`)
89+
- If you want the translationManager to detect duplicate message ids or not
90+
91+
### run
92+
93+
This will maintain all translation files. Based on your config you will get output for duplicate ids, and per specified language you will get the deleted translations, added messages (new messages that need to be translated), and not translated messages.

src/createSingleMessagesFile.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Path from 'path';
2+
import { writeFileSync } from 'fs';
3+
4+
export default ({
5+
messages,
6+
directory,
7+
fileName = 'defaultMessages.json',
8+
jsonSpaceIndentation = 2,
9+
}) => {
10+
if (!messages) throw new Error('messages are required');
11+
12+
if (!directory || typeof directory !== 'string' || directory.length === 0) {
13+
throw new Error('directory is required');
14+
}
15+
16+
const DIR = Path.join('./', directory, fileName);
17+
18+
writeFileSync(DIR, JSON.stringify(messages, null, jsonSpaceIndentation));
19+
};

src/getDefaultMessages.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// FROM:
2+
//
3+
// const files = [
4+
// {
5+
// path: 'src/components/Foo.json',
6+
// descriptors: [
7+
// {
8+
// id: 'foo_ok',
9+
// description: 'Ok text',
10+
// defaultMessage: 'OK',
11+
// },
12+
// {
13+
// id: 'foo_ok',
14+
// description: 'Submit text',
15+
// defaultMessage: 'Submit',
16+
// },
17+
// {
18+
// id: 'foo_cancel',
19+
// description: 'Cancel text',
20+
// defaultMessage: 'Cancel',
21+
// },
22+
// ],
23+
// },
24+
// ];
25+
//
26+
// TO:
27+
//
28+
// TODO: figure out what message gets returned for duplicate ids
29+
//
30+
// const result = {
31+
// messages: {
32+
// foo_ok: 'OK | Submit',
33+
// foo_cancel: 'Cancel',
34+
// },
35+
// duplicateIds: [
36+
// 'foo_ok',
37+
// ],
38+
// };
39+
40+
export default files => {
41+
if (!files) throw new Error('files are required');
42+
43+
return files.reduce((fileAcc, { descriptors }) => {
44+
const duplicateIds = fileAcc.duplicateIds;
45+
return {
46+
messages: descriptors.reduce((descAcc, { id, defaultMessage }) => {
47+
if (descAcc.hasOwnProperty(id)) { duplicateIds.push(id); }
48+
49+
return { ...descAcc, [id]: defaultMessage };
50+
}, fileAcc.messages),
51+
duplicateIds,
52+
};
53+
}, {
54+
messages: {},
55+
duplicateIds: [],
56+
});
57+
};

src/getLanguageReport.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// returns stats for a specific language
2+
// - added: contains all added messages
3+
// - untranslated: contains all untranslated messages
4+
// - deleted: contains all deleted messages
5+
// - fileOutput: contains output for the new language file
6+
// a single object with all added and untranslated messages
7+
// in a key (messageKey) value (message) format
8+
// - whitelistOutput: contains output for the new languageWhitelist file
9+
// all previously whitelisted keys, without the deleted keys
10+
//
11+
// {
12+
// added: [],
13+
// untranslated: [],
14+
// deleted: [],
15+
// fileOutput: {},
16+
// whitelistOutput: [],
17+
// }
18+
//
19+
// every message is declared in the following format
20+
// {
21+
// key: 'unique_message_key',
22+
// message: 'specific_message',
23+
// }
24+
25+
export default (defaultMessages, languageMessages, languageWhitelist) => {
26+
const result = {
27+
added: [],
28+
untranslated: [],
29+
deleted: [],
30+
fileOutput: {},
31+
whitelistOutput: [],
32+
};
33+
34+
const defaultMessageKeys = Object.keys(defaultMessages);
35+
36+
defaultMessageKeys.forEach(key => {
37+
const oldMessage = languageMessages[key];
38+
const defaultMessage = defaultMessages[key];
39+
40+
if (oldMessage) {
41+
result.fileOutput[key] = oldMessage;
42+
43+
if (oldMessage === defaultMessage) {
44+
if (languageWhitelist.indexOf(key) === -1) {
45+
result.untranslated.push({
46+
key,
47+
message: defaultMessage,
48+
});
49+
} else {
50+
result.whitelistOutput.push(key);
51+
}
52+
}
53+
} else {
54+
result.fileOutput[key] = defaultMessage;
55+
56+
result.added.push({
57+
key,
58+
message: defaultMessage,
59+
});
60+
}
61+
});
62+
63+
// if the key is still in the language file but no longer in the
64+
// defaultMessages file, then the key was deleted.
65+
result.deleted = Object.keys(languageMessages)
66+
.filter(key => defaultMessageKeys.indexOf(key) === -1)
67+
.map(key => ({
68+
key,
69+
message: languageMessages[key],
70+
}));
71+
72+
return result;
73+
};

src/getReports.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// returns a collection of reports for all languages
2+
// getReports: () => console.log('Not yet supported'),

src/index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export { default as readMessageFiles } from './readMessageFiles';
2+
export { default as createSingleMessagesFile } from './createSingleMessagesFile';
3+
export { default as getDefaultMessages } from './getDefaultMessages';
4+
5+
import run from './run';
6+
7+
export const createTranslationManager = ({
8+
messagesDirectory,
9+
translationsDirectory,
10+
singleMessagesFile = false,
11+
whitelistsDirectory = translationsDirectory,
12+
languages = [],
13+
detectDuplicateIds = true,
14+
}) => {
15+
if (!messagesDirectory || !translationsDirectory) {
16+
throw new Error('messagesDirectory and translationsDirectory are required');
17+
}
18+
19+
return () => run({
20+
messagesDirectory,
21+
translationsDirectory,
22+
singleMessagesFile,
23+
whitelistsDirectory,
24+
languages,
25+
detectDuplicateIds,
26+
});
27+
};

src/printResults.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { green, yellow, red, cyan } from 'chalk';
2+
3+
import { newLine, subheader, header } from './printer';
4+
5+
export default ({
6+
deleted,
7+
untranslated,
8+
added,
9+
}) => {
10+
if (!(deleted.length || added.length || untranslated.length)) {
11+
console.log(' ', green(' Perfectly maintained, no remarks!'));
12+
newLine();
13+
} else {
14+
if (deleted.length) {
15+
subheader('Deleted keys:');
16+
deleted.forEach(({ key, message }) => console.log(` ${red(key)}: ${cyan(message)}`));
17+
newLine();
18+
}
19+
20+
if (untranslated.length) {
21+
subheader('Untranslated keys:');
22+
untranslated.forEach(({ key, message }) => console.log(` ${yellow(key)}: ${cyan(message)}`));
23+
newLine();
24+
}
25+
26+
if (added.length) {
27+
subheader('Added keys:');
28+
added.forEach(({ key, message }) => console.log(` ${green(key)}: ${cyan(message)}`));
29+
newLine();
30+
}
31+
}
32+
};

src/printer.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { bold } from 'chalk';
2+
3+
export const newLine = () => console.log(' ');
4+
5+
export const header = title => {
6+
console.log(bold.underline(title));
7+
newLine();
8+
};
9+
10+
export const subheader = title => console.log(title);
11+
12+
export const footer = () => {
13+
newLine();
14+
};

src/readFile.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { readFileSync } from 'fs';
2+
3+
export default filename => {
4+
try {
5+
return readFileSync(filename, 'utf8');
6+
} catch (err) {
7+
return undefined;
8+
}
9+
};

0 commit comments

Comments
 (0)