@kununu/phraseapp-cli 4.0.0-beta.2 → 4.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README_CHECK_TRANSLATIONS.md +55 -0
- package/check-translations.js +138 -92
- package/package.json +4 -5
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# phraseapp-cli
|
|
2
|
+
>
|
|
3
|
+
> phraseapp-cli for <https://phraseapp.com/>
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Add `@kununu/phraseapp-cli` to your project
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @kununu/phraseapp-cli --save
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Add `check-translations` to your project
|
|
14
|
+
|
|
15
|
+
Add to ```package.json```
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
"check-translations": "SOURCE_DIR=src node ./node_modules/@kununu/phraseapp-cli/check-translations.js",
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Configuration file
|
|
22
|
+
|
|
23
|
+
You have to add to the ```.phraseapp.json``` file a new var dynamicKeys
|
|
24
|
+
|
|
25
|
+
**For example**:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"project_id": "YOUR_PROJECT_ID",
|
|
30
|
+
"path": "YOUR_TRANSLATIONS_PATH",
|
|
31
|
+
"locales": [
|
|
32
|
+
{
|
|
33
|
+
"locale_id": "de_DE",
|
|
34
|
+
"tags": [
|
|
35
|
+
"TAG1",
|
|
36
|
+
"TAG2",
|
|
37
|
+
"TAG3"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"locale_id": "de_AT",
|
|
42
|
+
"tags": [
|
|
43
|
+
(...)
|
|
44
|
+
],
|
|
45
|
+
"fallback_locale_id": "de_DE"
|
|
46
|
+
},
|
|
47
|
+
(...)
|
|
48
|
+
],
|
|
49
|
+
"dynamicKeys": []
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
Apache-2.0 © [kununu](https://kununu.com)
|
package/check-translations.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
1
|
+
const {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} = require('fs');
|
|
8
|
+
|
|
3
9
|
const {parse} = require('@babel/parser');
|
|
4
10
|
const traverse = require('@babel/traverse');
|
|
11
|
+
const {sync} = require('glob');
|
|
5
12
|
|
|
6
13
|
const FOLDER_REPORT_CHECK_TRANSLATIONS = `${process.cwd()}/report-check-translations`;
|
|
7
14
|
const UNUSED_KEYS_FILE = `${FOLDER_REPORT_CHECK_TRANSLATIONS}/not_used_keys.json`;
|
|
@@ -9,104 +16,143 @@ const MISSING_KEYS_FILE = `${FOLDER_REPORT_CHECK_TRANSLATIONS}/missing_keys.json
|
|
|
9
16
|
const POSSIBLE_DYNAMIC_KEYS_FILE = `${FOLDER_REPORT_CHECK_TRANSLATIONS}/possible_dynamic_keys.json`;
|
|
10
17
|
|
|
11
18
|
function extractTranslationKeys() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} else {
|
|
27
|
-
possibleDynamicKeys[file] = possibleDynamicKeys[file] || [];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
CallExpression({ node }) {
|
|
32
|
-
if (node.callee && node.callee.property && ['formatMessage'].includes(node.callee.property.name)) {
|
|
33
|
-
const firstArg = node.arguments[0];
|
|
34
|
-
if (firstArg && firstArg.type === 'ObjectExpression') {
|
|
35
|
-
const idProperty = firstArg.properties.find(prop => prop.key.name === 'id');
|
|
36
|
-
if (idProperty && idProperty.value.type === 'StringLiteral') {
|
|
37
|
-
keys.add(idProperty.value.value);
|
|
38
|
-
} else {
|
|
39
|
-
possibleDynamicKeys[file] = possibleDynamicKeys[file] || [];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error(`Error processing ${file}:`, error);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
19
|
+
const files = sync(
|
|
20
|
+
`${process.cwd()}/${process.env.SOURCE_DIR}/**/**/*.{js,jsx,ts,tsx}`,
|
|
21
|
+
);
|
|
22
|
+
const keys = new Set();
|
|
23
|
+
const possibleDynamicKeys = {};
|
|
24
|
+
|
|
25
|
+
files.forEach(file => {
|
|
26
|
+
const content = readFileSync(file, 'utf8');
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const ast = parse(content, {
|
|
30
|
+
plugins: ['jsx', 'typescript'],
|
|
31
|
+
sourceType: 'module',
|
|
32
|
+
});
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
34
|
+
traverse.default(ast, {
|
|
35
|
+
CallExpression({node}) {
|
|
36
|
+
if (
|
|
37
|
+
node.callee &&
|
|
38
|
+
node.callee.property &&
|
|
39
|
+
['formatMessage'].includes(node.callee.property.name)
|
|
40
|
+
) {
|
|
41
|
+
const firstArg = node.arguments[0];
|
|
42
|
+
|
|
43
|
+
if (firstArg && firstArg.type === 'ObjectExpression') {
|
|
44
|
+
const idProperty = firstArg.properties.find(
|
|
45
|
+
prop => prop.key.name === 'id',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (idProperty && idProperty.value.type === 'StringLiteral') {
|
|
49
|
+
keys.add(idProperty.value.value);
|
|
50
|
+
} else {
|
|
51
|
+
possibleDynamicKeys[file] = possibleDynamicKeys[file] || [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
JSXOpeningElement({node}) {
|
|
57
|
+
if (node.name.name === 'FormattedMessage') {
|
|
58
|
+
const idAttr = node.attributes.find(
|
|
59
|
+
attr => attr.name.name === 'id',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
idAttr &&
|
|
64
|
+
idAttr.value &&
|
|
65
|
+
idAttr.value.type === 'StringLiteral'
|
|
66
|
+
) {
|
|
67
|
+
keys.add(idAttr.value.value);
|
|
68
|
+
} else {
|
|
69
|
+
possibleDynamicKeys[file] = possibleDynamicKeys[file] || [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Error processing ${file}:`, error);
|
|
56
76
|
}
|
|
77
|
+
});
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
if (Object.keys(possibleDynamicKeys).length > 0) {
|
|
80
|
+
console.log(
|
|
81
|
+
`${Object.keys(possibleDynamicKeys).length} files contain possible dynamic keys were saved in ${POSSIBLE_DYNAMIC_KEYS_FILE}`,
|
|
82
|
+
);
|
|
83
|
+
writeFileSync(
|
|
84
|
+
POSSIBLE_DYNAMIC_KEYS_FILE,
|
|
85
|
+
JSON.stringify(possibleDynamicKeys, null, 2),
|
|
86
|
+
);
|
|
87
|
+
} else if (existsSync(POSSIBLE_DYNAMIC_KEYS_FILE)) {
|
|
88
|
+
unlinkSync(POSSIBLE_DYNAMIC_KEYS_FILE);
|
|
89
|
+
console.log(
|
|
90
|
+
`${POSSIBLE_DYNAMIC_KEYS_FILE} was deleted as there are no possible dynamic keys.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return keys;
|
|
59
95
|
}
|
|
60
96
|
|
|
61
97
|
function compareKeys() {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
// create report folder if it doesn't exist to save all the reports
|
|
99
|
+
if (!existsSync(FOLDER_REPORT_CHECK_TRANSLATIONS)) {
|
|
100
|
+
mkdirSync(FOLDER_REPORT_CHECK_TRANSLATIONS, {recursive: true});
|
|
101
|
+
}
|
|
102
|
+
const configPath = `${process.cwd()}/.phraseapp.json`;
|
|
103
|
+
const appKeys = extractTranslationKeys();
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const phrase = JSON.parse(readFileSync(configPath));
|
|
107
|
+
const translations = JSON.parse(
|
|
108
|
+
readFileSync(`${phrase.path}/de_AT.json`, 'utf8'),
|
|
109
|
+
);
|
|
110
|
+
const dynamicKeys = phrase.dynamicKeys || [];
|
|
111
|
+
|
|
112
|
+
const allValidKeys = new Set(Object.keys(translations));
|
|
113
|
+
|
|
114
|
+
let missingKeys = [...appKeys].filter(key => !allValidKeys.has(key));
|
|
115
|
+
|
|
116
|
+
missingKeys = missingKeys.filter(key => key != null && key !== undefined);
|
|
117
|
+
|
|
118
|
+
// Check if there are keys in dynamicKeys that are missing from allValidKeys.
|
|
119
|
+
dynamicKeys.forEach(key => {
|
|
120
|
+
if (!allValidKeys.has(key)) {
|
|
121
|
+
missingKeys.push(key);
|
|
122
|
+
} else {
|
|
123
|
+
allValidKeys.delete(key);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const referenceKeys = new Set([...appKeys, ...dynamicKeys]);
|
|
128
|
+
const unusedKeys = [...allValidKeys].filter(key => !referenceKeys.has(key));
|
|
129
|
+
|
|
130
|
+
if (missingKeys.length > 0) {
|
|
131
|
+
writeFileSync(MISSING_KEYS_FILE, JSON.stringify(missingKeys, null, 2));
|
|
132
|
+
console.log(
|
|
133
|
+
`${missingKeys.length} missing keys were saved in ${MISSING_KEYS_FILE}`,
|
|
134
|
+
);
|
|
135
|
+
} else if (existsSync(MISSING_KEYS_FILE)) {
|
|
136
|
+
unlinkSync(MISSING_KEYS_FILE);
|
|
137
|
+
console.log(
|
|
138
|
+
`${MISSING_KEYS_FILE} was deleted as there are no missing keys.`,
|
|
139
|
+
);
|
|
65
140
|
}
|
|
66
|
-
const configPath = `${process.cwd()}/.phraseapp.json`;
|
|
67
|
-
const appKeys = extractTranslationKeys();
|
|
68
141
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Check if there are keys in dynamicKeys that are missing from allValidKeys.
|
|
80
|
-
dynamicKeys.forEach(key => {
|
|
81
|
-
if (!allValidKeys.has(key)) {
|
|
82
|
-
missingKeys.push(key);
|
|
83
|
-
} else {
|
|
84
|
-
allValidKeys.delete(key);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
const referenceKeys = new Set([...appKeys, ...dynamicKeys]);
|
|
90
|
-
const unusedKeys = [...allValidKeys].filter(key => !referenceKeys.has(key));
|
|
91
|
-
|
|
92
|
-
if (missingKeys.length > 0) {
|
|
93
|
-
writeFileSync(MISSING_KEYS_FILE, JSON.stringify(missingKeys, null, 2));
|
|
94
|
-
console.log(`${missingKeys.length} missing keys were saved in ${MISSING_KEYS_FILE}`);
|
|
95
|
-
} else if (existsSync(MISSING_KEYS_FILE)) {
|
|
96
|
-
unlinkSync(MISSING_KEYS_FILE);
|
|
97
|
-
console.log(`${MISSING_KEYS_FILE} was deleted as there are no missing keys.`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (unusedKeys.length > 0) {
|
|
101
|
-
writeFileSync(UNUSED_KEYS_FILE, JSON.stringify(unusedKeys, null, 2));
|
|
102
|
-
console.log(`${unusedKeys.length} unused keys were saved in ${UNUSED_KEYS_FILE}`);
|
|
103
|
-
} else if (existsSync(UNUSED_KEYS_FILE)) {
|
|
104
|
-
unlinkSync(UNUSED_KEYS_FILE);
|
|
105
|
-
console.log(`${UNUSED_KEYS_FILE} was deleted as there are no missing keys.`);
|
|
106
|
-
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error(error);
|
|
142
|
+
if (unusedKeys.length > 0) {
|
|
143
|
+
writeFileSync(UNUSED_KEYS_FILE, JSON.stringify(unusedKeys, null, 2));
|
|
144
|
+
console.log(
|
|
145
|
+
`${unusedKeys.length} unused keys were saved in ${UNUSED_KEYS_FILE}`,
|
|
146
|
+
);
|
|
147
|
+
} else if (existsSync(UNUSED_KEYS_FILE)) {
|
|
148
|
+
unlinkSync(UNUSED_KEYS_FILE);
|
|
149
|
+
console.log(
|
|
150
|
+
`${UNUSED_KEYS_FILE} was deleted as there are no missing keys.`,
|
|
151
|
+
);
|
|
109
152
|
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(error);
|
|
155
|
+
}
|
|
110
156
|
}
|
|
111
157
|
|
|
112
|
-
compareKeys();
|
|
158
|
+
compareKeys();
|
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kununu/phraseapp-cli",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "kununu",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"lint": "eslint . --ext jsx --ext js --ext tsx --ext ts --ignore-path .eslintignore --max-warnings 10"
|
|
9
|
+
"lint": "eslint . --ext jsx --ext js --ext tsx --ext ts --ignore-path .eslintignore --max-warnings 10 --fix"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@babel/parser": "^7.26.10",
|
|
13
|
+
"@babel/traverse": "^7.26.10",
|
|
12
14
|
"colors": "1.4.0",
|
|
13
15
|
"dotenv": "16.4.5",
|
|
14
|
-
"@babel/parser": "^7.26.9",
|
|
15
|
-
"@babel/traverse": "^7.26.9",
|
|
16
|
-
"fs": "^0.0.1-security",
|
|
17
16
|
"glob": "^11.0.1"
|
|
18
17
|
},
|
|
19
18
|
"devDependencies": {
|