Skip to content

Commit

Permalink
Merge pull request #70 from GertjanReynaert/gr-overhaul
Browse files Browse the repository at this point in the history
Cleanup of default translationManager
  • Loading branch information
Gertjan Reynaert authored Dec 16, 2016
2 parents c5b69d6 + 438041a commit cc2fe74
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 78 deletions.
112 changes: 78 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,29 @@ now you know what messages you still need to update.
## Installing

```
npm install --save-dev react-intl-translations-manager
yarn add --dev react-intl-translations-manager
```

## Setup
### Basic

Since you need the `babel-plugin-react-intl` to extract the messages, I'll assume you're using babel in your project.

This is an example of the most basic usage of this plugin, in the API documentation below you can find more options.

Create a script in your package.json
```json
{
"scripts": {
"manage:translations": "babel-node ./translationRunner.js"
"manage:translations": "node ./translationRunner.js"
}
}
```
Create a file with your config you can run with the npm script
```js
// translationRunner.js
import manageTranslations from 'react-intl-translations-manager';
const manageTranslations = require('react-intl-translations-manager');

// es2015 import
// import manageTranslations from 'react-intl-translations-manager';

manageTranslations({
messagesDirectory: 'src/translations/extractedMessages',
Expand All @@ -69,11 +70,6 @@ Run the translation manager with your new npm script
npm run manage:translations
```

### Advanced

Build your own translationManager based on the core of this package, or it's
exposed helper methods.

## Usage

Now you can check the status of your translations by just running the script. Then
Expand All @@ -89,9 +85,13 @@ checking the translations status.

### manageTranslations

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 yet translated messages. It will also maintain a whitelist file per language where you can specify translation keys where the translation is identical to the default message. This way you can avoid untranslated message warnings for these messages.

You can optionally pass a printer object to this method. This way you can override the console logging with your own logging logic. If you want custom file writing logic, it is advised to roll your own translationManager based on the core.
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
yet translated messages. It will also maintain a whitelist file per language
where you can specify translation keys where the translation is identical to
the default message. This way you can avoid untranslated message warnings for
these messages.

#### Config

Expand All @@ -104,15 +104,17 @@ You can optionally pass a printer object to this method. This way you can overri
- example: `src/locales/lang`
- `whitelistsDirectory` (optional, default: `translationsDirectory`)
- Directory of the whitelist files the translation manager needs to maintain.
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
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
`unmaintained translation` warnings.
- example: `Dashboard` in english is also accepted as a valid translation for
dutch.
- `languages` (optional, default: `[]`)
- What languages the translation manager needs to maintain. Specifying no
languages actually doesn't make sense, but won't break the translationManager
either.
- example: for `['nl', 'fr']` the translation manager will maintain a `nl.json`, `fr.json`, `whitelist_nl.json` and a `whitelist_fr.json` file
- example: for `['nl', 'fr']` the translation manager will maintain a
`nl.json`, `fr.json`, `whitelist_nl.json` and a `whitelist_fr.json` file
- `singleMessagesFile` (optional, default: `false`)
- Option to output a single JSON file containing the aggregate of all extracted messages,
grouped by the file they were extracted from.
Expand All @@ -135,7 +137,8 @@ You can optionally pass a printer object to this method. This way you can overri
- If you want the translationManager to log duplicate message ids or not
- `sortKeys` (optional, default: `true`)
- If you want the translationManager to sort it's output, both json and console output
- `printers` (optional, default: {})
- `jsonOptions` (optional, default: { space: 2, trailingNewline: false })
- `overridePrinters` (optional, default: {})
- Here you can specify custom logging methods. If not specified a default printer is used.
- Possible printers to configure:
```js
Expand All @@ -146,30 +149,71 @@ You can optionally pass a printer object to this method. This way you can overri
printNoLanguageWhitelistFile: ( lang ) => { console.log(`No existing ${lang} file found. A new one is created.`) },
};
```
- `overrideCoreMethods` (optional, default: {})
- Here you can specify overrides for the core hooks. If not specified, the
default methods will be used.
- Possible overrides to configure:
```js
const overrideCoreMethods = {
provideExtractedMessages: () => {},
outputSingleFile: () => {},
outputDuplicateKeys: () => {},
beforeReporting: () => {},
provideLangTemplate: () => {},
provideTranslationsFile: () => {},
provideWhitelistFile: () => {},
reportLanguage: () => {},
afterReporting: () => {},
}
```

### core
```js
core(languages, hooks);
```
#### Fully configured

This is the core of the translationManager. It just takes a list of languages
and an object with all kinds of hooks it will execute when running. Below you
can find all hooks.
This is the config with all options applied:

```js
const hooks = {
provideExtractedMessages,
outputSingleFile,
outputDuplicateKeys,
beforeReporting,
provideLangTemplate,
provideTranslationsFile,
provideWhitelistFile,
reportLanguage,
afterReporting,
};
// import manageTranslations from 'react-intl-translations-manager';

manageTranslations({
messagesDirectory: 'src/translations/extractedMessages',
translationsDirectory: 'src/translations/locales/',
whitelistsDirectory: 'src/translations/locales/whitelists/',
languages: ['nl'], // any language you need
singleMessagesFile: true,
detectDuplicateIds: false,
sortKeys: false,
jsonOptions: {
space: 4,
trailingNewline: true,
},
overridePrinters: {
printDuplicateIds: ( duplicateIds ) => { console.log(`You have ${duplicateIds.length } duplicate IDs`) },
printLanguageReport: ( report ) => { console.log('Log report for a language') },
printNoLanguageFile: ( lang ) => { console.log(`No existing ${lang} translation file found. A new one is created.`) },
printNoLanguageWhitelistFile: ( lang ) => { console.log(`No existing ${lang} file found. A new one is created.`) },
},
overrideCoreMethods: {
provideExtractedMessages: () => {},
outputSingleFile: () => {},
outputDuplicateKeys: () => {},
beforeReporting: () => {},
provideLangTemplate: () => {},
provideTranslationsFile: () => {},
provideWhitelistFile: () => {},
reportLanguage: () => {},
afterReporting: () => {},
},
});
```

*This config is only as illustration for all possible options, these arent
recommended configuration options.

### CoreMethods

These are the core methods of the translationManager and what purpose they
serve.

#### provideExtractedMessages
```js
const extractedMessages = provideExtractedMessages();
Expand Down
1 change: 0 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { default } from './manageTranslations';

export { default as core } from './core';
export { default as readMessageFiles } from './readMessageFiles';
export { default as createSingleMessagesFile } from './createSingleMessagesFile';
export { default as getDefaultMessages } from './getDefaultMessages';
113 changes: 70 additions & 43 deletions src/manageTranslations.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import stringify from './stringify';

import core from './core';

const defaultJSONOptions = {
space: 2,
trailingNewline: false,
};

export default ({
messagesDirectory,
translationsDirectory,
Expand All @@ -20,21 +25,61 @@ export default ({
singleMessagesFile = false,
detectDuplicateIds = true,
sortKeys = true,
printers = {},
jsonSpaceIndentation = 2,
jsonTrailingNewline = false,
jsonOptions = {},
overridePrinters = {},
overrideCoreMethods = {},
}) => {
if (!messagesDirectory || !translationsDirectory) {
throw new Error('messagesDirectory and translationsDirectory are required');
}

const defaultPrinters = {
printDuplicateIds: duplicateIds => {
header('Duplicate ids:');
if (duplicateIds.length) {
duplicateIds.forEach(id => {
console.log(' ', `Duplicate message id: ${red(id)}`);
});
} else {
console.log(green(' No duplicate ids found, great!'));
}
footer();
},

printLanguageReport: langResults => {
header(`Maintaining ${yellow(langResults.languageFilename)}:`);
printResults({ ...langResults.report, sortKeys });
},

printNoLanguageFile: langResults => {
subheader(`
No existing ${langResults.languageFilename} translation file found.
A new one is created.
`);
},

printNoLanguageWhitelistFile: langResults => {
subheader(```
No existing ${langResults} file found.
A new one is created.
```);
},
};

const printers = {
...defaultPrinters,
...overridePrinters,
};

const stringifyOpts = {
space: jsonSpaceIndentation,
trailingNewline: jsonTrailingNewline,
...defaultJSONOptions,
...jsonOptions,
sortKeys,
};
core(languages, {

const defaultCoreMethods = {
provideExtractedMessages: () => readMessageFiles(messagesDirectory),

outputSingleFile: combinedFiles => {
if (singleMessagesFile) {
createSingleMessagesFile({
Expand All @@ -44,27 +89,18 @@ export default ({
});
}
},

outputDuplicateKeys: duplicateIds => {
if (detectDuplicateIds) {
if (typeof printers.printDuplicateIds === 'function') {
printers.printDuplicateIds(duplicateIds);
} else {
header('Duplicate ids:');
if (duplicateIds.length) {
duplicateIds.forEach(id => {
console.log(' ', `Duplicate message id: ${red(id)}`);
});
} else {
console.log(green(' No duplicate ids found, great!'));
}
footer();
}
}
if (detectDuplicateIds) return;

printers.printDuplicateIds(duplicateIds);
},

beforeReporting: () => {
mkdirpSync(translationsDirectory);
mkdirpSync(whitelistsDirectory);
},

provideLangTemplate: lang => {
const languageFilename = `${lang}.json`;
const languageFilepath = Path.join(translationsDirectory, languageFilename);
Expand All @@ -79,24 +115,22 @@ export default ({
whitelistFilepath,
};
},

provideTranslationsFile: lang => {
const filePath = Path.join(translationsDirectory, `${lang}.json`);
const jsonFile = readFile(filePath);
return jsonFile ? JSON.parse(jsonFile) : undefined;
},

provideWhitelistFile: lang => {
const filePath = Path.join(whitelistsDirectory, `whitelist_${lang}.json`);
const jsonFile = readFile(filePath);
return jsonFile ? JSON.parse(jsonFile) : undefined;
},

reportLanguage: langResults => {
if (!langResults.report.noTranslationFile && !langResults.report.noWhitelistFile) {
if (typeof printers.printLanguageReport === 'function') {
printers.printLanguageReport(langResults.languageFilename, langResults.report);
} else {
header(`Maintaining ${yellow(langResults.languageFilename)}:`);
printResults({ ...langResults.report, sortKeys });
}
printers.printLanguageReport(langResults);

writeFileSync(
langResults.languageFilepath,
Expand All @@ -108,29 +142,22 @@ export default ({
);
} else {
if (langResults.report.noTranslationFile) {
if (typeof printers.printNoLanguageFile === 'function') {
printers.printNoLanguageFile(langResults.lang);
} else {
subheader(```
No existing ${langResults.languageFilename} translation file found.
A new one is created.
```);
}
printers.printNoLanguageFile(langResults);
writeFileSync(langResults, stringify(langResults.report.fileOutput, stringifyOpts));
}

if (langResults.report.noWhitelistFile) {
if (typeof printers.printNoLanguageWhitelistFile === 'function') {
printers.printNoLanguageWhitelistFile(langResults.lang);
} else {
subheader(```
No existing ${langResults.whitelistFilename} file found.
A new one is created.
```);
}
printers.printNoLanguageWhitelistFile(langResults);
writeFileSync(langResults.whitelistFilepath, stringify([], stringifyOpts));
}
}
},

afterReporting: () => {},
};

core(languages, {
...defaultCoreMethods,
...overrideCoreMethods,
});
};

0 comments on commit cc2fe74

Please sign in to comment.