locize-cli 8.6.2 → 8.7.0
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/CHANGELOG.md +4 -0
- package/README.md +3 -3
- package/bin/locize +4 -4
- package/convertToFlatFormat.js +5 -0
- package/download.js +241 -82
- package/formats.js +2 -1
- package/getRemoteNamespace.js +19 -2
- package/lngs.json +1 -0
- package/package.json +4 -3
- package/parseLocalLanguage.js +50 -14
- package/parseLocalReference.js +1 -1
- package/sync.js +98 -31
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
7
7
|
|
|
8
|
+
## [8.7.0](https://github.com/locize/locize-cli/compare/v8.6.2...v8.7.0) - 2025-03-20
|
|
9
|
+
|
|
10
|
+
- intruduce xcstrings format [106](https://github.com/locize/locize-cli/issues/106)
|
|
11
|
+
|
|
8
12
|
## [8.6.2](https://github.com/locize/locize-cli/compare/v8.6.1...v8.6.2) - 2025-03-18
|
|
9
13
|
|
|
10
14
|
- fix migrate command to handle paged update
|
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ or
|
|
|
84
84
|
locize download
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties)
|
|
87
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
90
|
locize download --project-id my-project-id-93e1-442a-ab35-24331fa294ba --ver latest --language en --namespace namespace1 --path ./backup --format android
|
|
@@ -133,7 +133,7 @@ Add your api-key and your project-id and let's go...
|
|
|
133
133
|
locize sync --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties)
|
|
136
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
137
137
|
|
|
138
138
|
```sh
|
|
139
139
|
locize sync --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba --format android
|
|
@@ -192,7 +192,7 @@ Add your api-key and your project-id and let's go...
|
|
|
192
192
|
locize save-missing --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties)
|
|
195
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
196
196
|
|
|
197
197
|
```sh
|
|
198
198
|
locize save-missing --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba --format android
|
package/bin/locize
CHANGED
|
@@ -232,7 +232,7 @@ program
|
|
|
232
232
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
233
233
|
.option('-g, --get-path <url>', `Specify the get-path url that should be used (default: ${getPathUrl})`)
|
|
234
234
|
.option('-k, --api-key <apiKey>', 'The api-key that should be used')
|
|
235
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties])', 'json')
|
|
235
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
236
236
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: true)', 'true')
|
|
237
237
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
238
238
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
@@ -361,7 +361,7 @@ program
|
|
|
361
361
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
362
362
|
.option('-B, --backup-deleted-path <path>', 'Saves the segments that will be deleted in this path')
|
|
363
363
|
.option('-A, --auto-create-path <true|false>', 'This will automatically make sure the --path is created. (default: true)', 'true')
|
|
364
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties])', 'json')
|
|
364
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
365
365
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: false)', 'false')
|
|
366
366
|
.option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
|
|
367
367
|
.option('-cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
|
|
@@ -472,7 +472,7 @@ program
|
|
|
472
472
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
473
473
|
.option('-v, --ver <version>', 'Found namespaces will be matched to this version (default: latest)')
|
|
474
474
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
475
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties])', 'json')
|
|
475
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
476
476
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
477
477
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
478
478
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
@@ -695,7 +695,7 @@ program
|
|
|
695
695
|
.command('format [fileOrDirectory]')
|
|
696
696
|
.alias('ft')
|
|
697
697
|
.description('format local files')
|
|
698
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties])', 'json')
|
|
698
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
699
699
|
.option('-l, --reference-language <lng>', 'Some format conversions need to know the reference language.', 'en')
|
|
700
700
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
701
701
|
.option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${configInWorkingDirectory} or ${configInHome})`)
|
package/convertToFlatFormat.js
CHANGED
|
@@ -13,6 +13,7 @@ const ftl2js = require('fluent_conv/cjs/ftl2js');
|
|
|
13
13
|
const tmx2js = require('tmexchange/cjs/tmx2js');
|
|
14
14
|
const laravel2js = require('laravelphp/cjs/laravel2js');
|
|
15
15
|
const javaProperties = require('@js.properties/properties');
|
|
16
|
+
const xcstrings2locize = require('locize-xcstrings/cjs/xcstrings2locize');
|
|
16
17
|
const flatten = require('flat');
|
|
17
18
|
const prepareCombinedImport = require('./combineSubkeyPreprocessor').prepareImport;
|
|
18
19
|
|
|
@@ -293,6 +294,10 @@ const convertToFlatFormat = (opt, data, lng, cb) => {
|
|
|
293
294
|
cb(null, javaProperties.parseToProperties(data.toString()));
|
|
294
295
|
return;
|
|
295
296
|
}
|
|
297
|
+
if (opt.format === 'xcstrings') {
|
|
298
|
+
cb(null, xcstrings2locize(data.toString()));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
296
301
|
cb(new Error(`${opt.format} is not a valid format!`));
|
|
297
302
|
} catch (err) {
|
|
298
303
|
cb(err);
|
package/download.js
CHANGED
|
@@ -12,6 +12,15 @@ const convertToDesiredFormat = require('./convertToDesiredFormat');
|
|
|
12
12
|
const formats = require('./formats');
|
|
13
13
|
const getProjectStats = require('./getProjectStats');
|
|
14
14
|
const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
|
|
15
|
+
const locize2xcstrings = require('locize-xcstrings/cjs/locize2xcstrings');
|
|
16
|
+
|
|
17
|
+
function getInfosInUrl(download) {
|
|
18
|
+
const splitted = download.key.split('/');
|
|
19
|
+
const version = splitted[download.isPrivate ? 2 : 1];
|
|
20
|
+
const language = splitted[download.isPrivate ? 3 : 2];
|
|
21
|
+
const namespace = splitted[download.isPrivate ? 4 : 3];
|
|
22
|
+
return { version, language, namespace };
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
function handleDownload(opt, url, err, res, downloads, cb) {
|
|
17
26
|
if (err || (downloads && (downloads.errorMessage || downloads.message))) {
|
|
@@ -34,113 +43,263 @@ function handleDownload(opt, url, err, res, downloads, cb) {
|
|
|
34
43
|
return;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
opt.isPrivate = download.isPrivate;
|
|
43
|
-
|
|
44
|
-
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
45
|
-
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
46
|
+
if (opt.format === 'xcstrings') { // 1 file per namespace including all languages
|
|
47
|
+
const downloadsByNamespace = {};
|
|
48
|
+
downloads.forEach((download) => {
|
|
49
|
+
const { version, namespace } = getInfosInUrl(download);
|
|
50
|
+
opt.isPrivate = download.isPrivate;
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
downloadsByNamespace[version] = downloadsByNamespace[version] || {};
|
|
53
|
+
downloadsByNamespace[version][namespace] = downloadsByNamespace[version][namespace] || [];
|
|
54
|
+
downloadsByNamespace[version][namespace].push(download);
|
|
55
|
+
});
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
async.eachSeries(Object.keys(downloadsByNamespace), (version, clb) => {
|
|
58
|
+
async.eachLimit(Object.keys(downloadsByNamespace[version]), 20, (ns, clb) => {
|
|
59
|
+
if (opt.namespace && opt.namespace !== ns) return clb(null);
|
|
60
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(ns) < 0) return clb(null);
|
|
61
|
+
|
|
62
|
+
const locizeData = {
|
|
63
|
+
sourceLng: opt.referenceLanguage,
|
|
64
|
+
resources: {}
|
|
65
|
+
};
|
|
66
|
+
async.eachLimit(downloadsByNamespace[version][ns], 20, (download, clb2) => {
|
|
67
|
+
const { language } = getInfosInUrl(download);
|
|
68
|
+
getRemoteNamespace(opt, language, ns, (err, ns, lastModified) => {
|
|
69
|
+
if (err) return clb2(err);
|
|
70
|
+
|
|
71
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
72
|
+
return clb2(null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
locizeData.resources[language] = ns;
|
|
76
|
+
clb2();
|
|
77
|
+
});
|
|
78
|
+
}, (err) => {
|
|
79
|
+
if (err) return clb(err);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const result = locize2xcstrings(locizeData);
|
|
83
|
+
const converted = JSON.stringify(result, null, 2);
|
|
84
|
+
|
|
85
|
+
var filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, ns) + reversedFileExtensionsMap[opt.format];
|
|
86
|
+
var mkdirPath;
|
|
87
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
88
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function logAndClb(err) {
|
|
92
|
+
if (err) return clb(err);
|
|
93
|
+
if (!cb) console.log(colors.green(`downloaded ${version}/${ns} to ${opt.path}...`));
|
|
94
|
+
if (clb) clb(null);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!opt.version) {
|
|
98
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
99
|
+
fs.writeFile(path.join(opt.path, version, filledMask), converted, logAndClb);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
104
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, logAndClb);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
107
|
+
return clb(err);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}, clb);
|
|
111
|
+
}, (err) => {
|
|
112
|
+
if (err) {
|
|
113
|
+
if (!cb) {
|
|
114
|
+
console.error(colors.red(err.message));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
if (cb) cb(err);
|
|
118
|
+
return;
|
|
52
119
|
}
|
|
53
120
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!opt.language) {
|
|
70
|
-
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
71
|
-
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
72
|
-
return;
|
|
121
|
+
if (cb) cb(null);
|
|
122
|
+
});
|
|
123
|
+
} else { // 1 file per namespace/lng
|
|
124
|
+
async.eachLimit(downloads, 20, (download, clb) => {
|
|
125
|
+
const { version, language, namespace } = getInfosInUrl(download);
|
|
126
|
+
opt.isPrivate = download.isPrivate;
|
|
127
|
+
|
|
128
|
+
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
129
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
130
|
+
|
|
131
|
+
getRemoteNamespace(opt, language, namespace, (err, ns, lastModified) => {
|
|
132
|
+
if (err) return clb(err);
|
|
133
|
+
|
|
134
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
135
|
+
return clb(null);
|
|
73
136
|
}
|
|
74
137
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
138
|
+
convertToDesiredFormat(opt, namespace, language, ns, lastModified, (err, converted) => {
|
|
139
|
+
if (err) {
|
|
140
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
141
|
+
return clb(err);
|
|
142
|
+
}
|
|
143
|
+
var filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, language).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
144
|
+
var mkdirPath;
|
|
145
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
146
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
147
|
+
}
|
|
148
|
+
if (!opt.version) {
|
|
149
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
150
|
+
fs.writeFile(path.join(opt.path, version, filledMask), converted, clb);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!opt.language) {
|
|
154
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
155
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + language, '');
|
|
160
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
161
|
+
mkdirp.sync(parentDir);
|
|
162
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
163
|
+
});
|
|
79
164
|
});
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
165
|
+
}, (err) => {
|
|
166
|
+
if (err) {
|
|
167
|
+
if (!cb) {
|
|
168
|
+
console.error(colors.red(err.message));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
if (cb) cb(err);
|
|
172
|
+
return;
|
|
86
173
|
}
|
|
87
|
-
if (cb) cb(err);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
174
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
175
|
+
if (!cb) console.log(colors.green(`downloaded ${url} to ${opt.path}...`));
|
|
176
|
+
if (cb) cb(null);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
94
179
|
}
|
|
95
180
|
|
|
96
181
|
function handlePull(opt, toDownload, cb) {
|
|
97
182
|
const url = opt.apiPath + '/pull/' + opt.projectId + '/' + opt.version;
|
|
98
|
-
async.eachLimit(toDownload, 5, (download, clb) => {
|
|
99
|
-
const lng = download.language;
|
|
100
|
-
const namespace = download.namespace;
|
|
101
183
|
|
|
102
|
-
|
|
103
|
-
|
|
184
|
+
if (opt.format === 'xcstrings') { // 1 file per namespace including all languages
|
|
185
|
+
const downloadsByNamespace = {};
|
|
186
|
+
toDownload.forEach((download) => {
|
|
187
|
+
const { namespace } = download;
|
|
188
|
+
downloadsByNamespace[namespace] = downloadsByNamespace[namespace] || [];
|
|
189
|
+
downloadsByNamespace[namespace].push(download);
|
|
190
|
+
});
|
|
104
191
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
192
|
+
async.eachLimit(Object.keys(downloadsByNamespace), 5, (namespace, clb) => {
|
|
193
|
+
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
194
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
108
195
|
|
|
109
|
-
|
|
110
|
-
|
|
196
|
+
const locizeData = {
|
|
197
|
+
sourceLng: opt.referenceLanguage,
|
|
198
|
+
resources: {}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
async.eachLimit(downloadsByNamespace[namespace], 5, (download, clb2) => {
|
|
202
|
+
const { language } = download;
|
|
203
|
+
opt.raw = true;
|
|
204
|
+
getRemoteNamespace(opt, language, namespace, (err, ns, lastModified) => {
|
|
205
|
+
if (err) return clb2(err);
|
|
206
|
+
|
|
207
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
208
|
+
return clb2(null);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
locizeData.resources[language] = ns;
|
|
212
|
+
clb2();
|
|
213
|
+
});
|
|
214
|
+
}, (err) => {
|
|
215
|
+
if (err) return clb(err);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const result = locize2xcstrings(locizeData);
|
|
219
|
+
const converted = JSON.stringify(result, null, 2);
|
|
220
|
+
|
|
221
|
+
var filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
222
|
+
var mkdirPath;
|
|
223
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
224
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function logAndClb(err) {
|
|
228
|
+
if (err) return clb(err);
|
|
229
|
+
if (!cb) console.log(colors.green(`downloaded ${opt.version}/${namespace} to ${opt.path}...`));
|
|
230
|
+
if (clb) clb(null);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
234
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, logAndClb);
|
|
235
|
+
} catch (e) {
|
|
111
236
|
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
112
237
|
return clb(err);
|
|
113
238
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
239
|
+
});
|
|
240
|
+
}, (err) => {
|
|
241
|
+
if (err) {
|
|
242
|
+
if (!cb) {
|
|
243
|
+
console.error(colors.red(err.message));
|
|
244
|
+
process.exit(1);
|
|
118
245
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
246
|
+
if (cb) cb(err);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (cb) cb(null);
|
|
251
|
+
});
|
|
252
|
+
} else { // 1 file per namespace/lng
|
|
253
|
+
async.eachLimit(toDownload, 5, (download, clb) => {
|
|
254
|
+
const lng = download.language;
|
|
255
|
+
const namespace = download.namespace;
|
|
256
|
+
|
|
257
|
+
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
258
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
259
|
+
|
|
260
|
+
getRemoteNamespace(opt, lng, namespace, (err, ns, lastModified) => {
|
|
261
|
+
if (err) return clb(err);
|
|
262
|
+
|
|
263
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
264
|
+
return clb(null);
|
|
123
265
|
}
|
|
124
266
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
267
|
+
convertToDesiredFormat(opt, namespace, lng, ns, lastModified, (err, converted) => {
|
|
268
|
+
if (err) {
|
|
269
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
270
|
+
return clb(err);
|
|
271
|
+
}
|
|
272
|
+
var filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
273
|
+
var mkdirPath;
|
|
274
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
275
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
276
|
+
}
|
|
277
|
+
if (!opt.language) {
|
|
278
|
+
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
279
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + lng, '');
|
|
284
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
285
|
+
mkdirp.sync(parentDir);
|
|
286
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
287
|
+
});
|
|
129
288
|
});
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
289
|
+
}, (err) => {
|
|
290
|
+
if (err) {
|
|
291
|
+
if (!cb) {
|
|
292
|
+
console.error(colors.red(err.message));
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
if (cb) cb(err);
|
|
296
|
+
return;
|
|
136
297
|
}
|
|
137
|
-
if (cb) cb(err);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
298
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
299
|
+
if (!cb) console.log(colors.green(`downloaded ${url} to ${opt.path}...`));
|
|
300
|
+
if (cb) cb(null);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
144
303
|
}
|
|
145
304
|
|
|
146
305
|
const handleError = (err, cb) => {
|
package/formats.js
CHANGED
|
@@ -13,7 +13,8 @@ const fileExtensionsMap = {
|
|
|
13
13
|
'.ftl': ['fluent'],
|
|
14
14
|
'.tmx': ['tmx'],
|
|
15
15
|
'.php': ['laravel'],
|
|
16
|
-
'.properties': ['properties']
|
|
16
|
+
'.properties': ['properties'],
|
|
17
|
+
'.xcstrings': ['xcstrings']
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const acceptedFileExtensions = Object.keys(fileExtensionsMap);
|
package/getRemoteNamespace.js
CHANGED
|
@@ -4,9 +4,26 @@ const sortFlatResources = require('./sortFlatResources');
|
|
|
4
4
|
|
|
5
5
|
const getRandomDelay = (delayFrom, delayTo) => Math.floor(Math.random() * delayTo) + delayFrom;
|
|
6
6
|
|
|
7
|
+
function onlyKeysFlat(resources, prefix, ret) {
|
|
8
|
+
if (!resources) resources;
|
|
9
|
+
ret = ret || {};
|
|
10
|
+
Object.keys(resources).forEach((k) => {
|
|
11
|
+
if (typeof resources[k] === 'string' || !resources[k] || typeof resources[k].value === 'string') {
|
|
12
|
+
if (prefix) {
|
|
13
|
+
ret[prefix + '.' + k] = resources[k];
|
|
14
|
+
} else {
|
|
15
|
+
ret[k] = resources[k];
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
onlyKeysFlat(resources[k], prefix ? prefix + '.' + k : k, ret);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return ret;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
const pullNamespacePaged = (opt, lng, ns, cb, next, retry) => {
|
|
8
25
|
next = next || '';
|
|
9
|
-
request(opt.apiPath + '/pull/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns + '?' + 'next=' + next + '&ts=' + Date.now(), {
|
|
26
|
+
request(opt.apiPath + '/pull/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns + '?' + 'next=' + next + (opt.raw ? '&raw=true' : '') + '&ts=' + Date.now(), {
|
|
10
27
|
method: 'get',
|
|
11
28
|
headers: {
|
|
12
29
|
'Authorization': opt.apiKey
|
|
@@ -31,7 +48,7 @@ const pullNamespacePaged = (opt, lng, ns, cb, next, retry) => {
|
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
cb(null, {
|
|
34
|
-
result: sortFlatResources(flatten(obj)),
|
|
51
|
+
result: opt.raw ? sortFlatResources(onlyKeysFlat(obj)) : sortFlatResources(flatten(obj)),
|
|
35
52
|
next: res.headers.get('x-next-page'),
|
|
36
53
|
lastModified: res.headers.get('last-modified') ? new Date(res.headers.get('last-modified')) : undefined
|
|
37
54
|
});
|
package/lngs.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locize-cli",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
4
4
|
"description": "locize cli to import locales",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"ini": "4.1.3",
|
|
24
24
|
"js-yaml": "4.1.0",
|
|
25
25
|
"laravelphp": "2.0.4",
|
|
26
|
+
"locize-xcstrings": "1.0.0",
|
|
26
27
|
"lodash.clonedeep": "4.5.0",
|
|
27
28
|
"mkdirp": "3.0.1",
|
|
28
29
|
"node-fetch": "2.7.0",
|
|
@@ -34,9 +35,9 @@
|
|
|
34
35
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"@yao-pkg/pkg": "6.3.1",
|
|
37
39
|
"eslint": "8.56.0",
|
|
38
|
-
"gh-release": "7.0.2"
|
|
39
|
-
"@yao-pkg/pkg": "6.3.1"
|
|
40
|
+
"gh-release": "7.0.2"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"lint": "eslint .",
|
package/parseLocalLanguage.js
CHANGED
|
@@ -6,6 +6,7 @@ const convertToFlatFormat = require('./convertToFlatFormat');
|
|
|
6
6
|
const formats = require('./formats');
|
|
7
7
|
const fileExtensionsMap = formats.fileExtensionsMap;
|
|
8
8
|
const acceptedFileExtensions = formats.acceptedFileExtensions;
|
|
9
|
+
const xcstrings2locize = require('locize-xcstrings/cjs/xcstrings2locize');
|
|
9
10
|
|
|
10
11
|
const getFiles = (srcpath) => {
|
|
11
12
|
return fs.readdirSync(srcpath).filter((file) => {
|
|
@@ -21,7 +22,7 @@ const getDirectories = (srcpath) => {
|
|
|
21
22
|
|
|
22
23
|
const parseLocalLanguage = (opt, lng, cb) => {
|
|
23
24
|
const hasNamespaceInPath = opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) > -1;
|
|
24
|
-
const filledLngMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng);
|
|
25
|
+
const filledLngMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, opt.format === 'xcstrings' ? '' : lng);
|
|
25
26
|
var firstPartLngMask, lastPartLngMask;
|
|
26
27
|
if (opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`) > opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`)) {
|
|
27
28
|
const secondPartMask = opt.pathMask.substring(opt.pathMask.lastIndexOf(path.sep) + 1);
|
|
@@ -138,28 +139,63 @@ const parseLocalLanguage = (opt, lng, cb) => {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
if (opt.format === 'xcstrings') { // 1 file per namespace including all languages
|
|
143
|
+
try {
|
|
144
|
+
const content = xcstrings2locize(JSON.parse(data));
|
|
145
|
+
|
|
146
|
+
fs.stat(fPath, (err, stat) => {
|
|
147
|
+
if (err) return clb(err);
|
|
148
|
+
|
|
149
|
+
clb(null, Object.keys(content.resources).map((l) => ({
|
|
150
|
+
namespace: namespace,
|
|
151
|
+
path: fPath,
|
|
152
|
+
extension: fExt,
|
|
153
|
+
content: content.resources[l],
|
|
154
|
+
language: l,
|
|
155
|
+
mtime: stat.mtime
|
|
156
|
+
})));
|
|
157
|
+
});
|
|
158
|
+
} catch (e) {
|
|
143
159
|
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
144
160
|
err.message += '\n' + fPath;
|
|
145
161
|
return clb(err);
|
|
146
162
|
}
|
|
163
|
+
} else { // 1 file per namespace/lng
|
|
164
|
+
convertToFlatFormat(opt, data, lng, (err, content) => {
|
|
165
|
+
if (err) {
|
|
166
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
167
|
+
err.message += '\n' + fPath;
|
|
168
|
+
return clb(err);
|
|
169
|
+
}
|
|
147
170
|
|
|
148
|
-
|
|
149
|
-
|
|
171
|
+
fs.stat(fPath, (err, stat) => {
|
|
172
|
+
if (err) return clb(err);
|
|
150
173
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
clb(null, {
|
|
175
|
+
namespace: namespace,
|
|
176
|
+
path: fPath,
|
|
177
|
+
extension: fExt,
|
|
178
|
+
content: content,
|
|
179
|
+
language: lng,
|
|
180
|
+
mtime: stat.mtime
|
|
181
|
+
});
|
|
158
182
|
});
|
|
159
183
|
});
|
|
160
|
-
}
|
|
184
|
+
}
|
|
161
185
|
});
|
|
162
|
-
},
|
|
186
|
+
}, (err, ret) => {
|
|
187
|
+
if (err) return cb(err);
|
|
188
|
+
// xcstrings, returns array in array
|
|
189
|
+
const r = ret.reduce((prev, cur) => {
|
|
190
|
+
if (Array.isArray(cur)) {
|
|
191
|
+
prev = prev.concat(cur);
|
|
192
|
+
} else {
|
|
193
|
+
prev.push(cur);
|
|
194
|
+
}
|
|
195
|
+
return prev;
|
|
196
|
+
}, []);
|
|
197
|
+
cb(null, r);
|
|
198
|
+
});
|
|
163
199
|
};
|
|
164
200
|
|
|
165
201
|
module.exports = parseLocalLanguage;
|
package/parseLocalReference.js
CHANGED
|
@@ -4,7 +4,7 @@ const filterNamespaces = require('./filterNamespaces');
|
|
|
4
4
|
const parseLocalReference = (opt, cb) => parseLocalLanguage(opt, opt.referenceLanguage, (err, nss) => {
|
|
5
5
|
if (err) return cb(err);
|
|
6
6
|
|
|
7
|
-
cb(err, filterNamespaces(opt, nss));
|
|
7
|
+
cb(err, filterNamespaces(opt, nss).filter((n) => n.language === opt.referenceLanguage));
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
module.exports = parseLocalReference;
|
package/sync.js
CHANGED
|
@@ -17,6 +17,7 @@ const lngCodes = require('./lngs.json');
|
|
|
17
17
|
const deleteNamespace = require('./deleteNamespace');
|
|
18
18
|
const getProjectStats = require('./getProjectStats');
|
|
19
19
|
const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
|
|
20
|
+
const locize2xcstrings = require('locize-xcstrings/cjs/locize2xcstrings');
|
|
20
21
|
|
|
21
22
|
const getDirectories = (srcpath) => {
|
|
22
23
|
return fs.readdirSync(srcpath).filter((file) => {
|
|
@@ -24,6 +25,14 @@ const getDirectories = (srcpath) => {
|
|
|
24
25
|
});
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
function getInfosInUrl(download) {
|
|
29
|
+
const splitted = download.key.split('/');
|
|
30
|
+
const version = splitted[download.isPrivate ? 2 : 1];
|
|
31
|
+
const language = splitted[download.isPrivate ? 3 : 2];
|
|
32
|
+
const namespace = splitted[download.isPrivate ? 4 : 3];
|
|
33
|
+
return { version, language, namespace };
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
const getDownloads = (opt, cb) => {
|
|
28
37
|
if (!opt.unpublished) {
|
|
29
38
|
request(opt.apiPath + '/download/' + opt.projectId + '/' + opt.version, {
|
|
@@ -194,7 +203,7 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
|
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
if (!opt.dry) cleanupLanguages(opt, remoteLanguages);
|
|
206
|
+
if (!opt.dry && opt.format !== 'xcstrings') cleanupLanguages(opt, remoteLanguages);
|
|
198
207
|
|
|
199
208
|
getDownloads(opt, (err, downloads) => {
|
|
200
209
|
if (err) return cb(err);
|
|
@@ -208,43 +217,101 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
|
|
|
208
217
|
return lng !== opt.referenceLanguage;
|
|
209
218
|
});
|
|
210
219
|
}
|
|
211
|
-
async.eachLimit(downloads, opt.unpublished ? 5 : 20, (download, clb) => {
|
|
212
|
-
const splitted = download.key.split('/');
|
|
213
|
-
const lng = splitted[download.isPrivate ? 3 : 2];
|
|
214
|
-
const namespace = splitted[download.isPrivate ? 4 : 3];
|
|
215
|
-
opt.isPrivate = download.isPrivate;
|
|
216
|
-
|
|
217
|
-
if (opt.language && opt.language !== lng && lng !== opt.referenceLanguage) return clb(null);
|
|
218
|
-
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(lng) < 0 && lng !== opt.referenceLanguage) return clb(null);
|
|
219
|
-
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
220
|
-
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
221
|
-
|
|
222
|
-
getRemoteNamespace(opt, lng, namespace, (err, ns, lastModified) => {
|
|
223
|
-
if (err) return clb(err);
|
|
224
|
-
|
|
225
|
-
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
226
|
-
return clb(null);
|
|
227
|
-
}
|
|
228
220
|
|
|
229
|
-
|
|
221
|
+
if (opt.format === 'xcstrings') { // 1 file per namespace including all languages
|
|
222
|
+
const downloadsByNamespace = {};
|
|
223
|
+
downloads.forEach((download) => {
|
|
224
|
+
const { namespace } = getInfosInUrl(download);
|
|
225
|
+
downloadsByNamespace[namespace] = downloadsByNamespace[namespace] || [];
|
|
226
|
+
downloadsByNamespace[namespace].push(download);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
async.eachLimit(Object.keys(downloadsByNamespace), opt.unpublished ? 5 : 20, (namespace, clb) => {
|
|
230
|
+
const locizeData = {
|
|
231
|
+
sourceLng: opt.referenceLanguage,
|
|
232
|
+
resources: {}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
async.eachLimit(downloadsByNamespace[namespace], opt.unpublished ? 5 : 20, (download, clb) => {
|
|
236
|
+
const { language } = getInfosInUrl(download);
|
|
237
|
+
opt.isPrivate = download.isPrivate;
|
|
238
|
+
|
|
239
|
+
if (opt.language && opt.language !== language && language !== opt.referenceLanguage) return clb(null);
|
|
240
|
+
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(language) < 0 && language !== opt.referenceLanguage) return clb(null);
|
|
241
|
+
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
242
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
243
|
+
|
|
244
|
+
if (opt.unpublished) opt.raw = true;
|
|
245
|
+
getRemoteNamespace(opt, language, namespace, (err, ns, lastModified) => {
|
|
246
|
+
if (err) return clb(err);
|
|
247
|
+
|
|
248
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
249
|
+
return clb(null);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (manipulate && typeof manipulate == 'function') manipulate(language, namespace, ns);
|
|
253
|
+
|
|
254
|
+
locizeData.resources[language] = ns;
|
|
255
|
+
clb();
|
|
256
|
+
});
|
|
257
|
+
}, (err) => {
|
|
258
|
+
if (err) return clb(err);
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = locize2xcstrings(locizeData);
|
|
262
|
+
const converted = JSON.stringify(result, null, 2);
|
|
230
263
|
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
265
|
+
if (opt.dry) return clb(null);
|
|
266
|
+
if (opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`) > opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) && filledMask.lastIndexOf(path.sep) > 0) {
|
|
267
|
+
mkdirp.sync(path.join(opt.path, filledMask.substring(0, filledMask.lastIndexOf(path.sep))));
|
|
268
|
+
}
|
|
269
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
270
|
+
mkdirp.sync(parentDir);
|
|
271
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
272
|
+
} catch (e) {
|
|
233
273
|
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
234
274
|
return clb(err);
|
|
235
275
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
276
|
+
});
|
|
277
|
+
}, cb);
|
|
278
|
+
} else { // 1 file per namespace/lng
|
|
279
|
+
async.eachLimit(downloads, opt.unpublished ? 5 : 20, (download, clb) => {
|
|
280
|
+
const { language, namespace } = getInfosInUrl(download);
|
|
281
|
+
opt.isPrivate = download.isPrivate;
|
|
282
|
+
|
|
283
|
+
if (opt.language && opt.language !== language && language !== opt.referenceLanguage) return clb(null);
|
|
284
|
+
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(language) < 0 && language !== opt.referenceLanguage) return clb(null);
|
|
285
|
+
if (opt.namespace && opt.namespace !== namespace) return clb(null);
|
|
286
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return clb(null);
|
|
287
|
+
|
|
288
|
+
getRemoteNamespace(opt, language, namespace, (err, ns, lastModified) => {
|
|
289
|
+
if (err) return clb(err);
|
|
290
|
+
|
|
291
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
292
|
+
return clb(null);
|
|
241
293
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
294
|
+
|
|
295
|
+
if (manipulate && typeof manipulate == 'function') manipulate(language, namespace, ns);
|
|
296
|
+
|
|
297
|
+
convertToDesiredFormat(opt, namespace, language, ns, lastModified, (err, converted) => {
|
|
298
|
+
if (err) {
|
|
299
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
300
|
+
return clb(err);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, language).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
304
|
+
if (opt.dry) return clb(null);
|
|
305
|
+
if (opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`) > opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) && filledMask.lastIndexOf(path.sep) > 0) {
|
|
306
|
+
mkdirp.sync(path.join(opt.path, filledMask.substring(0, filledMask.lastIndexOf(path.sep))));
|
|
307
|
+
}
|
|
308
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
309
|
+
mkdirp.sync(parentDir);
|
|
310
|
+
fs.writeFile(path.join(opt.path, filledMask), converted, clb);
|
|
311
|
+
});
|
|
245
312
|
});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
313
|
+
}, cb);
|
|
314
|
+
}
|
|
248
315
|
});
|
|
249
316
|
};
|
|
250
317
|
|