Skip to content

Commit f856484

Browse files
authored
Merge pull request #200 from MartijnCuppens/missing-translation-string
Missing translation string
2 parents 382f5e5 + 75e52b7 commit f856484

14 files changed

+129
-43
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- [`separator`](#separator)
3737
- [`exclude`](#exclude)
3838
- [`noEmptyTranslation`](#noemptytranslation)
39+
- [`missingtranslationstring`](#missingtranslationstring)
3940
- [Supported `vue-i18n` Formats](#supported-vue-i18n-formats)
4041
- [Why?](#why)
4142
- [Contribution](#contribution)
@@ -208,6 +209,18 @@ You can generate a default configuration file using `npx vue-i18n-extract init`
208209
* `'en'`: Generate empty default translation for locale `'en'`.
209210
* `'en-US'`: Generate empty default translation for locale `'en-US'`.
210211

212+
### `missingTranslationString`
213+
214+
* Name: `missingTranslationString`
215+
* CLI argument: `--missing-translation-string`, `--missingTranslationString`
216+
* Required: No
217+
* Default: `''`
218+
* Type: `string` or `null`
219+
* Description: Text to use when missing translations are added to the translation files.
220+
* Examples:
221+
* `'Translation missing'`: Use "Translation missing" as default key.
222+
* `null`: Add the translation key to the file, but don't add a default translation. This will trigger `vue-i18n`'s the missingHandler.
223+
211224
## Supported `vue-i18n` Formats
212225

213226
- Static in template or script:

bin/vue-i18n-extract.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ cli
4343
'--noEmptyTranslation',
4444
'Use if you want to generate a default translated string by using the key itself'
4545
)
46+
.option(
47+
'--missingTranslationString',
48+
'Default string for missing translations.'
49+
)
4650
.option(
4751
'--detect <detectionType>',
4852
'[string] The type of issues you want to detect (ex. --detect missing) ',

dist/config-file/vue-i18n-extract.config.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ declare const _default: {
88
ci: boolean;
99
separator: string;
1010
noEmptyTranslation: string;
11+
missingTranslationString: string;
1112
};
1213
export default _default;

dist/create-report/language-files.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
import { SimpleFile, I18NLanguage, I18NItem } from '../types';
33
export declare function readLanguageFiles(src: string): SimpleFile[];
44
export declare function extractI18NLanguageFromLanguageFiles(languageFiles: SimpleFile[], dot?: DotObject.Dot): I18NLanguage;
5-
export declare function writeMissingToLanguageFiles(parsedLanguageFiles: SimpleFile[], missingKeys: I18NItem[], dot?: DotObject.Dot, noEmptyTranslation?: string): void;
5+
export declare function writeMissingToLanguageFiles(parsedLanguageFiles: SimpleFile[], missingKeys: I18NItem[], dot?: DotObject.Dot, noEmptyTranslation?: string, missingTranslationString?: string): void;
66
export declare function removeUnusedFromLanguageFiles(parsedLanguageFiles: SimpleFile[], unusedKeys: I18NItem[], dot?: DotObject.Dot): void;
77
export declare function parselanguageFiles(languageFiles: string, dot?: DotObject.Dot): I18NLanguage;

dist/create-report/report.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/// <reference types="node" />
2-
import { I18NItemWithBounding, I18NLanguage, I18NReport } from '../types';
3-
export declare function extractI18NReport(vueItems: I18NItemWithBounding[], languageFiles: I18NLanguage): I18NReport;
2+
import { DetectionType, I18NItemWithBounding, I18NLanguage, I18NReport } from '../types';
3+
export declare function extractI18NReport(vueItems: I18NItemWithBounding[], languageFiles: I18NLanguage, detect: DetectionType[]): I18NReport;
44
export declare function writeReportToFile(report: I18NReport, writePath: string): Promise<NodeJS.ErrnoException | void>;

dist/types.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ export declare type ReportOptions = {
88
ci?: boolean;
99
separator?: string;
1010
noEmptyTranslation?: string;
11+
missingTranslationString?: string;
12+
detect?: DetectionType[];
1113
};
14+
export declare enum DetectionType {
15+
Missing = "missing",
16+
Unused = "unused",
17+
Dynamic = "dynamic"
18+
}
1219
export declare type SimpleFile = {
1320
fileName: string;
1421
path: string;

dist/vue-i18n-extract.modern.mjs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ var defaultConfig = {
3434
remove: false,
3535
ci: false,
3636
separator: '.',
37-
noEmptyTranslation: ''
37+
noEmptyTranslation: '',
38+
missingTranslationString: ''
3839
};
3940

4041
function initCommand() {
@@ -61,6 +62,14 @@ function resolveConfig() {
6162
return options;
6263
}
6364

65+
var DetectionType;
66+
67+
(function (DetectionType) {
68+
DetectionType["Missing"] = "missing";
69+
DetectionType["Unused"] = "unused";
70+
DetectionType["Dynamic"] = "dynamic";
71+
})(DetectionType || (DetectionType = {}));
72+
6473
function readVueFiles(src) {
6574
// Replace backslash path segments to make the path work with the glob package.
6675
// https://github.com/Spittal/vue-i18n-extract/issues/159
@@ -218,13 +227,13 @@ function extractI18NLanguageFromLanguageFiles(languageFiles, dot = Dot) {
218227
return accumulator;
219228
}, {});
220229
}
221-
function writeMissingToLanguageFiles(parsedLanguageFiles, missingKeys, dot = Dot, noEmptyTranslation = '') {
230+
function writeMissingToLanguageFiles(parsedLanguageFiles, missingKeys, dot = Dot, noEmptyTranslation = '', missingTranslationString = '') {
222231
parsedLanguageFiles.forEach(languageFile => {
223232
const languageFileContent = JSON.parse(languageFile.content);
224233
missingKeys.forEach(item => {
225234
if (item.language && languageFile.fileName.includes(item.language) || !item.language) {
226235
const addDefaultTranslation = noEmptyTranslation && (noEmptyTranslation === '*' || noEmptyTranslation === item.language);
227-
dot.str(item.path, addDefaultTranslation ? item.path : '', languageFileContent);
236+
dot.str(item.path, addDefaultTranslation ? item.path : missingTranslationString === 'null' ? null : missingTranslationString, languageFileContent);
228237
}
229238
});
230239
writeLanguageFile(languageFile, languageFileContent);
@@ -278,20 +287,31 @@ function mightBeDynamic(item) {
278287
} // Looping through the arays multiple times might not be the most effecient, but it's the easiest to read and debug. Which at this scale is an accepted trade-off.
279288

280289

281-
function extractI18NReport(vueItems, languageFiles) {
290+
function extractI18NReport(vueItems, languageFiles, detect) {
282291
const missingKeys = [];
283292
const unusedKeys = [];
284-
const maybeDynamicKeys = vueItems.filter(vueItem => mightBeDynamic(vueItem)).map(vueItem => stripBounding(vueItem));
293+
const maybeDynamicKeys = [];
294+
295+
if (detect.includes(DetectionType.Dynamic)) {
296+
maybeDynamicKeys.push(...vueItems.filter(vueItem => mightBeDynamic(vueItem)).map(vueItem => stripBounding(vueItem)));
297+
}
298+
285299
Object.keys(languageFiles).forEach(language => {
286300
const languageItems = languageFiles[language];
287-
const missingKeysInLanguage = vueItems.filter(vueItem => !mightBeDynamic(vueItem)).filter(vueItem => !languageItems.some(languageItem => vueItem.path === languageItem.path)).map(vueItem => _extends({}, stripBounding(vueItem), {
288-
language
289-
}));
290-
const unusedKeysInLanguage = languageItems.filter(languageItem => !vueItems.some(vueItem => languageItem.path === vueItem.path || languageItem.path.startsWith(vueItem.path + '.'))).map(languageItem => _extends({}, languageItem, {
291-
language
292-
}));
293-
missingKeys.push(...missingKeysInLanguage);
294-
unusedKeys.push(...unusedKeysInLanguage);
301+
302+
if (detect.includes(DetectionType.Missing)) {
303+
const missingKeysInLanguage = vueItems.filter(vueItem => !mightBeDynamic(vueItem)).filter(vueItem => !languageItems.some(languageItem => vueItem.path === languageItem.path)).map(vueItem => _extends({}, stripBounding(vueItem), {
304+
language
305+
}));
306+
missingKeys.push(...missingKeysInLanguage);
307+
}
308+
309+
if (detect.includes(DetectionType.Unused)) {
310+
const unusedKeysInLanguage = languageItems.filter(languageItem => !vueItems.some(vueItem => languageItem.path === vueItem.path || languageItem.path.startsWith(vueItem.path + '.'))).map(languageItem => _extends({}, languageItem, {
311+
language
312+
}));
313+
unusedKeys.push(...unusedKeysInLanguage);
314+
}
295315
});
296316
return {
297317
missingKeys,
@@ -323,16 +343,25 @@ async function createI18NReport(options) {
323343
exclude = [],
324344
ci,
325345
separator,
326-
noEmptyTranslation = ''
346+
noEmptyTranslation = '',
347+
missingTranslationString = '',
348+
detect = [DetectionType.Missing, DetectionType.Unused, DetectionType.Dynamic]
327349
} = options;
328350
if (!vueFilesGlob) throw new Error('Required configuration vueFiles is missing.');
329351
if (!languageFilesGlob) throw new Error('Required configuration languageFiles is missing.');
352+
let issuesToDetect = Array.isArray(detect) ? detect : [detect];
353+
const invalidDetectOptions = issuesToDetect.filter(item => !Object.values(DetectionType).includes(item));
354+
355+
if (invalidDetectOptions.length) {
356+
throw new Error(`Invalid 'detect' value(s): ${invalidDetectOptions}`);
357+
}
358+
330359
const dot = typeof separator === 'string' ? new Dot(separator) : Dot;
331360
const vueFiles = readVueFiles(path.resolve(process.cwd(), vueFilesGlob));
332361
const languageFiles = readLanguageFiles(path.resolve(process.cwd(), languageFilesGlob));
333362
const I18NItems = extractI18NItemsFromVueFiles(vueFiles);
334363
const I18NLanguage = extractI18NLanguageFromLanguageFiles(languageFiles, dot);
335-
const report = extractI18NReport(I18NItems, I18NLanguage);
364+
const report = extractI18NReport(I18NItems, I18NLanguage, issuesToDetect);
336365
report.unusedKeys = report.unusedKeys.filter(key => !exclude.filter(excluded => key.path.startsWith(excluded)).length);
337366
if (report.missingKeys.length) console.info('\nMissing Keys'), console.table(report.missingKeys);
338367
if (report.unusedKeys.length) console.info('\nUnused Keys'), console.table(report.unusedKeys);
@@ -349,7 +378,7 @@ async function createI18NReport(options) {
349378
}
350379

351380
if (add && report.missingKeys.length) {
352-
writeMissingToLanguageFiles(languageFiles, report.missingKeys, dot, noEmptyTranslation);
381+
writeMissingToLanguageFiles(languageFiles, report.missingKeys, dot, noEmptyTranslation, missingTranslationString);
353382
console.info('\nThe missing keys have been added to your language files.');
354383
}
355384

@@ -373,5 +402,5 @@ process.on('unhandledRejection', err => {
373402
process.exit(1);
374403
});
375404

376-
export { createI18NReport, extractI18NItemsFromVueFiles, extractI18NLanguageFromLanguageFiles, extractI18NReport, initCommand, parseVueFiles, parselanguageFiles, readLanguageFiles, readVueFiles, removeUnusedFromLanguageFiles, resolveConfig, writeMissingToLanguageFiles, writeReportToFile };
405+
export { DetectionType, createI18NReport, extractI18NItemsFromVueFiles, extractI18NLanguageFromLanguageFiles, extractI18NReport, initCommand, parseVueFiles, parselanguageFiles, readLanguageFiles, readVueFiles, removeUnusedFromLanguageFiles, resolveConfig, writeMissingToLanguageFiles, writeReportToFile };
377406
//# sourceMappingURL=vue-i18n-extract.modern.mjs.map

dist/vue-i18n-extract.modern.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/vue-i18n-extract.umd.js

Lines changed: 45 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/vue-i18n-extract.umd.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)