-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Expand file tree
/
Copy pathcopy-i18n-tags.js
More file actions
178 lines (144 loc) · 7.15 KB
/
copy-i18n-tags.js
File metadata and controls
178 lines (144 loc) · 7.15 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
require('coffee-script').register()
const fs = require('fs')
const path = require('path')
const PWD = __dirname
const enTranslations = require(`../app/locale/en`).translation
const { exec } = require('child_process')
// TODO: better identification of empty sections after deleting entries. Empy sections yield module load fails on run.
require('./generateRot13Locale')
function escapeRegexp (s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
}
const enSourceFile = fs.readFileSync(
path.join(PWD, `../app/locale/en.js`),
{ encoding: 'utf8' }
)
const CHANGE_MARKER = '{change}'
const CATEGORY_SPLIT_PATTERN = /^[\s\n]*(?=[^:\n]+:\s*$)/gm // One or more new lines followed by "key:", followed by newline
const CATEGORY_CAPTURE_PATTERN = /^([^:\n]+):\s*\n/ // Extracts category name from first line of category section
const COMMENTS_PATTERN = /^[\s\n]*([^:\n]+):\s*"[^#\n"]*"\s*#(.*)$/gm // Find lines with comments, capture key / value / comment
const CHANGE_PATTERN = new RegExp(`\\s?\\s?(#\\s)?${escapeRegexp(CHANGE_MARKER)}`, 'gi') // Identify translation marked change
const QUOTE_TAG_NAME_PATTERN = /^[a-z0-9_]+$/i // Determines if tag name needs to be quoted
const enSplitByCategory = enSourceFile.split(CATEGORY_SPLIT_PATTERN)
const comments = {}
// Extract comments per translation so we can add back later
for (const section of enSplitByCategory) {
const categoryMatch = CATEGORY_CAPTURE_PATTERN.exec(section)
if (categoryMatch) {
const categoryName = categoryMatch[1]
comments[categoryName] = comments[categoryName] || {}
let comment
while ((comment = COMMENTS_PATTERN.exec(section)) !== null) {
comments[categoryName][comment[1]] = comment[2].trim()
}
}
}
// Grab all locale files that we need to manage
const IGNORE_FILES = ['rot13.js', 'en.js', 'locale.js']
const localeFiles = fs
.readdirSync(path.join(PWD, '../app/locale'))
.filter(fileName => fileName.endsWith('.js') && IGNORE_FILES.indexOf(fileName) === -1)
for (const localeFile of localeFiles) {
console.log(`Processing ${localeFile}`)
// Load raw source file
const localeSource = fs.readFileSync(
path.join(PWD, `../app/locale/${localeFile}`),
{ encoding: 'utf8' }
)
// Load locale
const localeContents = require(`../app/locale/${localeFile}`)
const localeTranslations = localeContents.translation || {}
// Initial rewrite of file with first line
const rewrittenLines = [
`module.exports = {\n nativeDescription: "${localeContents.nativeDescription}",\n englishDescription: ` +
`"${localeContents.englishDescription}",\n translation: {`,
' }\n}\n' // endline
]
// For each category within the locale
for (const enCategoryName of Object.keys(enTranslations)) {
const enCategory = enTranslations[enCategoryName]
const catIsPresent = typeof localeTranslations[enCategoryName] !== 'undefined'
const localeCategory = localeTranslations[enCategoryName] || {}
// Prefix for regular expressions that require the pattern to exist within a category. This depends on
// categories and their tags to not contain new lines and categories being separated by a newline. This regex
// is intended to be used as a prefix for regular expressions looking for a specific tag. It is used to
// make sure the tag belongs to the current category. It does so by ensuring that there is a category name
// in the locale file, followed by one or more non empty lines. You can then append any tag specififc regex
// to this expression to obtain a regular expression that pattern matches a specific tag within a category.
const categoryRegexPrefix = `\\s\\s${escapeRegexp(enCategoryName)}:\\n(?:.+\\n)*`
rewrittenLines.splice(-1, 0, '') // insert at last second line (last line is close bracket)
// Add the category line, commenting it out if it does not exist in the locale file
const categoryCommentPrefix = (!catIsPresent) ? '//' : ''
rewrittenLines.splice(-1, 0, `${categoryCommentPrefix} ${enCategoryName}: {`)
rewrittenLines.splice(-1, 0, `${categoryCommentPrefix } },`)
// For each tag within the category
for (const enTagName of Object.keys(enCategory)) {
const localeTranslation = localeCategory[enTagName];
const tagIsPresent = (typeof localeTranslation !== 'undefined')
const sourceFileTag = (QUOTE_TAG_NAME_PATTERN.test(enTagName)) ? enTagName : `"${enTagName}"`
// Prepare the comment for the tag if it exists. Note that this will propagate {change} tag from en locale
let comment = ''
if (comments[enCategoryName] && comments[enCategoryName][enTagName]) {
comment = comments[enCategoryName][enTagName]
}
const commentedTagRegex = new RegExp(categoryRegexPrefix + `\s*//\\s+${escapeRegexp(sourceFileTag)}:`)
if (localeSource.search(commentedTagRegex) >= 0) {
// If the translation is commented out in the locale fine, make sure it is not marked as changed. A
// translation is not marked as changed until it is uncommented in a locale file. Once it is
// uncommented in a translation file, the translation is considered active and changes should be
// tracked
comment = comment.replace(CHANGE_PATTERN, '')
} else {
const tagIsMarkedChangeRegex = new RegExp(
categoryRegexPrefix +
`\\s+"?${escapeRegexp(sourceFileTag)}"?:` +
`\\s".*"\\s*#.*${escapeRegexp(CHANGE_MARKER)}\\s*`,
'mi' // Case insensitive to support "change" in different capitalizations
)
// If en locale file has tag marked as change and the current locale file does not
// have it marked as change, update the current locale file to add change marker
if (
localeSource.search(tagIsMarkedChangeRegex) >= 0 &&
comment.search(CHANGE_PATTERN) === -1
) {
comment += ` ${CHANGE_MARKER}`
}
}
comment = comment.trim()
if (comment.length > 0) {
comment = `// ${comment}`
}
// If the tag does not exist in the locale file, make sure it is commented out
const lineCommentPrefix = (!tagIsPresent) ? '//' : ''
// Stringify the output to escape special chars
const finalLocaleTranslation = JSON.stringify(
localeTranslation || enCategory[enTagName]
)
rewrittenLines.splice(-2, 0, // insert at second to last line, since 2 } elements there
`${lineCommentPrefix} ${sourceFileTag}: ${finalLocaleTranslation}, ${comment}`.trimRight()
)
}
}
// Write the new file contents to the locale file
const newLocaleContents = rewrittenLines.join('\n') + '\n' // End file with a new line
fs.writeFileSync(
path.join(PWD, `../app/locale/${localeFile}`),
newLocaleContents,
{ encoding: 'utf8' }
)
}
// Remove change tags from english now that they have been propagated
const rewrittenEnSource = enSourceFile.replace(CHANGE_PATTERN, '')
fs.writeFileSync(
path.join(PWD, `../app/locale/en.js`),
rewrittenEnSource
)
console.log('running eslint --fix app/locale/')
exec('npx eslint --fix app/locale/', (err) => {
if (err) {
console.error('linting failed', err)
} else {
console.log('linting succeeded')
}
console.log('Done!')
})