native-document 1.0.83 → 1.0.84
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/i18n.js +1 -0
- package/package.json +4 -1
- package/src/i18n/bin/scan.js +132 -0
- package/src/i18n/service/I18nService.js +46 -0
- package/src/i18n/service/functions.js +29 -0
package/i18n.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/i18n/service/I18nService'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-document",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.84",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -20,5 +20,8 @@
|
|
|
20
20
|
"eslint": "^9.33.0",
|
|
21
21
|
"magic-string": "^0.30.21",
|
|
22
22
|
"rollup": "^4.53.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"i18next": "^25.8.0"
|
|
23
26
|
}
|
|
24
27
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import Fs from 'node:fs';
|
|
4
|
+
import Path from 'node:path';
|
|
5
|
+
const configFile = Path.resolve('i18n.scanner.config.json');
|
|
6
|
+
|
|
7
|
+
const Debug = {
|
|
8
|
+
$warning: [],
|
|
9
|
+
warn(category, message, data) {
|
|
10
|
+
Debug.$warning.push({ message: `[${category}] ${message}`, data });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function scanFolder(dirPath, callback) {
|
|
15
|
+
const files = Fs.readdirSync(dirPath);
|
|
16
|
+
|
|
17
|
+
files.forEach((file) => {
|
|
18
|
+
const filePath = Path.join(dirPath, file);
|
|
19
|
+
const stats = Fs.statSync(filePath);
|
|
20
|
+
|
|
21
|
+
if (stats.isDirectory()) {
|
|
22
|
+
scanFolder(filePath, callback);
|
|
23
|
+
} else if (stats.isFile()) {
|
|
24
|
+
// If it's a file, add its path to the list
|
|
25
|
+
callback(filePath);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function scanFile(filePath) {
|
|
31
|
+
const fileContent = Fs.readFileSync(filePath, 'utf-8');
|
|
32
|
+
const keys = [];
|
|
33
|
+
fileContent.replace(/\btr\((.*?)\)/g, function(_, args) {
|
|
34
|
+
const key = getKey(args, filePath);
|
|
35
|
+
if(key) {
|
|
36
|
+
keys.push(key);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return keys;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getKey(args, filePath) {
|
|
43
|
+
const trimmed = args.trim();
|
|
44
|
+
const firstChar = trimmed[0];
|
|
45
|
+
if(firstChar !== '"' && firstChar !== "'") {
|
|
46
|
+
Debug.warn('Key Extract', '', {
|
|
47
|
+
message: `can't extract key from ("${args}") in file.`,
|
|
48
|
+
args: args,
|
|
49
|
+
file: filePath
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const key = [];
|
|
55
|
+
let i = 1;
|
|
56
|
+
while(i < trimmed.length) {
|
|
57
|
+
const char = trimmed[i];
|
|
58
|
+
if(char === '\\') {
|
|
59
|
+
key.push(trimmed[++i]);
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if(char === firstChar) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
key.push(char);
|
|
68
|
+
}
|
|
69
|
+
return key.join('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function scan() {
|
|
73
|
+
if(!Fs.existsSync(configFile)) {
|
|
74
|
+
console.error('Config file not found. Please create i18n.scanner.config.json file in your project root directory.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const keys = [];
|
|
79
|
+
try {
|
|
80
|
+
const configContent = Fs.readFileSync(configFile, 'utf-8');
|
|
81
|
+
const config = JSON.parse(configContent);
|
|
82
|
+
const initialPath = Path.resolve(process.cwd(), config?.scan?.dir || 'src');
|
|
83
|
+
scanFolder(initialPath, (filePath) => {
|
|
84
|
+
const fileExtensions = config?.scan?.extensions || ['js'];
|
|
85
|
+
const shouldScanFile = fileExtensions.some(extension => filePath.endsWith(extension));
|
|
86
|
+
if(!shouldScanFile) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
keys.push(...scanFile(filePath));
|
|
90
|
+
});
|
|
91
|
+
if(!config?.locales) {
|
|
92
|
+
console.log('No locales found in config file.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(":: scan locales");
|
|
97
|
+
scanFolder(config.locales, (filePath) => {
|
|
98
|
+
if(!filePath.endsWith('.json')) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const locale = Path.basename(filePath, '.json');
|
|
102
|
+
const localeData = JSON.parse(Fs.readFileSync(filePath, 'utf-8'));
|
|
103
|
+
const localeKeys = Object.keys(localeData);
|
|
104
|
+
const absentKeys = keys.filter(key => !localeKeys.includes(key));
|
|
105
|
+
|
|
106
|
+
const result = absentKeys.reduce((acc, key) => {
|
|
107
|
+
acc[key] = key;
|
|
108
|
+
return acc;
|
|
109
|
+
}, {});
|
|
110
|
+
|
|
111
|
+
console.log("-- " + locale + ` : ${absentKeys.length} keys are absent in locale file.`);
|
|
112
|
+
if(!config?.save) {
|
|
113
|
+
console.log('-- display result:');
|
|
114
|
+
console.log(result);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const saveFile = config.save+'/'+(locale+'.absent.json');
|
|
119
|
+
const saveFilePath = Path.resolve(process.cwd(), saveFile);
|
|
120
|
+
console.log('-- save result to ' + saveFile);
|
|
121
|
+
const dirName = Path.dirname(saveFilePath);
|
|
122
|
+
if(!Fs.existsSync(saveFilePath)) {
|
|
123
|
+
Fs.mkdirSync(dirName, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
Fs.writeFileSync(saveFilePath, JSON.stringify(result, null, 4));
|
|
126
|
+
});
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.log(e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
scan();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import i18next from "i18next";
|
|
2
|
+
import {Observable} from "../../../index";
|
|
3
|
+
import {hasObservableParams, getObservableParams, getParams} from "./functions";
|
|
4
|
+
|
|
5
|
+
const I18nService = (function () {
|
|
6
|
+
const defaultLang = import.meta.env.VITE_LOCALE || 'en';
|
|
7
|
+
const $current = Observable(defaultLang);
|
|
8
|
+
i18next.on('languageChanged', (lng) => $current.set(lng));
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
init(resources) {
|
|
12
|
+
return i18next.init({
|
|
13
|
+
lng: import.meta.env.VITE_LOCALE,
|
|
14
|
+
fallbackLng: import.meta.env.VITE_FALLBACK_LANGUE || 'en',
|
|
15
|
+
debug: import.meta.env.VITE_ENV === 'development',
|
|
16
|
+
resources
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
current: $current,
|
|
20
|
+
use(lng) {
|
|
21
|
+
return i18next.changeLanguage(lng);
|
|
22
|
+
},
|
|
23
|
+
tr(key, ...args) {
|
|
24
|
+
return i18next.t(key, ...args) || key;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}());
|
|
28
|
+
|
|
29
|
+
const tr = (key, params = {}) => {
|
|
30
|
+
const isContainObservable = hasObservableParams(params);
|
|
31
|
+
const observable = isContainObservable
|
|
32
|
+
? Observable.computed(() => I18nService.tr(key, getParams(params)), getObservableParams(params))
|
|
33
|
+
: Observable(I18nService.tr(key, params));
|
|
34
|
+
|
|
35
|
+
i18next.on('languageChanged', (lng) => {
|
|
36
|
+
const data = isContainObservable ? getParams(params) : params;
|
|
37
|
+
observable.set(I18nService.tr(key, data));
|
|
38
|
+
});
|
|
39
|
+
return observable;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
String.prototype.tr = function(params = {}) {
|
|
43
|
+
return tr(this, params);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { I18nService, tr };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Observable} from "../../../index";
|
|
2
|
+
import Validator from "../../core/utils/validator";
|
|
3
|
+
|
|
4
|
+
export const getParams = (params) => {
|
|
5
|
+
if(!params) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const key in params) {
|
|
11
|
+
result[key] = Observable.value(params[key]);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const hasObservableParams = (params) => {
|
|
17
|
+
if(!params) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return Validator.isObject(params)
|
|
21
|
+
&& Object.values(params).some(value => Validator.isObservable(value));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getObservableParams = (params) => {
|
|
25
|
+
if(!params) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return Object.values(params).filter(value => Validator.isObservable(value));
|
|
29
|
+
};
|