/
index.js
135 lines (121 loc) 路 3.73 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const yargs = require('yargs');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const args = {
SRC: 'src',
LOCALES: 'locales',
MULTI_FILES: 'multi-files',
SINGLE_FILE: 'single-file',
};
const { argv } = yargs
.string(args.SRC)
.default(args.SRC, '.source_strings.json')
.describe(args.SRC, 'Path to source.')
.string(args.LOCALES)
.default(args.LOCALES, 'src/i18n/locales')
.describe(args.LOCALES, 'Path to locale object / module.')
.string(args.MULTI_FILES)
.default(args.MULTI_FILES, null)
.describe(
args.MULTI_FILES,
'Folder path (relative to root) where multi file translations will be stored. If omitted a single file will be created / used.',
)
.string(args.SINGLE_FILE)
.default(args.SINGLE_FILE, 'translation_input.json')
.describe(args.SINGLE_FILE, 'File path when using single translation file.');
const safeRequire = modulePath => {
try {
// eslint-disable-next-line global-require
return require(modulePath);
} catch {
return false;
}
};
const sourceStrings = safeRequire(path.join(process.cwd(), argv[args.SRC]));
assert(
sourceStrings,
'Could not find source (defaults to .source_string.json).',
);
const { phrases } = sourceStrings;
const hashToTexts = phrases.reduce(
(acc, phrase) => ({ ...acc, ...(phrase.hashToText || {}) }),
{},
);
const locales = Object.keys(
safeRequire(path.join(process.cwd(), argv[args.LOCALES])) || {
en_US: {},
},
);
// OPEN POINT: Should we update translation when the locale is "en_US" (default)?
const updateTranslations = translations => {
const hashesToAdd = Object.keys(hashToTexts || []).filter(
hash => !Object.keys(translations).includes(hash),
);
const hashesToRemove = Object.keys(translations || []).filter(
hash => !Object.keys(hashToTexts).includes(hash),
);
const updatedTranslations = { ...translations };
hashesToAdd.forEach(hash => {
if (!updatedTranslations[hash]) {
updatedTranslations[hash] = {
translations: [{ translation: hashToTexts[hash] }],
};
}
});
hashesToRemove.forEach(hash => {
if (updatedTranslations[hash]) {
delete updatedTranslations[hash];
}
});
return updatedTranslations;
};
if (argv[args.MULTI_FILES]) {
locales.forEach(locale => {
const translationsFilePath = path.join(
process.cwd(),
argv[args.MULTI_FILES],
`${locale}.json`,
);
const translationsObj = safeRequire(translationsFilePath) || {
'fb-locale': locale,
translations: {},
};
translationsObj.translations = updateTranslations(
translationsObj.translations,
);
fs.writeFileSync(
translationsFilePath,
JSON.stringify(translationsObj, null, 2),
);
});
} else {
const translationsFilePath = path.join(process.cwd(), argv[args.SINGLE_FILE]);
const translationsObj = safeRequire(translationsFilePath) || {
phrases: [],
translationGroups: [],
};
// Update phrases
// OPEN POINT: Are there cases where we don't want to just replace phrases with those from source
translationsObj.phrases = phrases;
// Update translations for each locale
locales.forEach(locale => {
const localeMatch = group => group['fb-locale'] === locale;
const translationsForLocaleObj = translationsObj.translationGroups.find(
localeMatch,
) || {
'fb-locale': locale,
translations: {},
};
translationsForLocaleObj.translations = updateTranslations(
translationsForLocaleObj.translations,
);
if (translationsObj.translationGroups.findIndex(localeMatch) === -1) {
translationsObj.translationGroups.push(translationsForLocaleObj);
}
});
fs.writeFileSync(
translationsFilePath,
JSON.stringify(translationsObj, null, 2),
);
}