locize-cli 8.7.0 → 9.0.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 +9 -0
- package/README.md +13 -4
- package/bin/locize +4 -4
- package/convertToDesiredFormat.js +12 -2
- package/convertToFlatFormat.js +18 -3
- package/download.js +11 -7
- package/format.js +3 -1
- package/formats.js +2 -2
- package/package.json +1 -1
- package/sync.js +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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
|
+
## [9.0.0](https://github.com/locize/locize-cli/compare/v8.7.1...v9.0.0) - 2025-04-03
|
|
9
|
+
|
|
10
|
+
- the format `yaml-rails-ns`/`yml-rails-ns` is the old `yaml-rails`/`yml-rails`
|
|
11
|
+
- `yaml-rails`/`yml-rails` does not scope the yaml content with the namespace anymore => use `yaml-rails-ns`/`yml-rails-ns` instead
|
|
12
|
+
|
|
13
|
+
## [8.7.1](https://github.com/locize/locize-cli/compare/v8.7.0...v8.7.1) - 2025-04-02
|
|
14
|
+
|
|
15
|
+
- make generated files POSIX compliant, addresses [107](https://github.com/locize/locize-cli/issues/107)
|
|
16
|
+
|
|
8
17
|
## [8.7.0](https://github.com/locize/locize-cli/compare/v8.6.2...v8.7.0) - 2025-03-20
|
|
9
18
|
|
|
10
19
|
- intruduce xcstrings format [106](https://github.com/locize/locize-cli/issues/106)
|
package/README.md
CHANGED
|
@@ -84,7 +84,11 @@ 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, xcstrings)
|
|
87
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
88
|
+
|
|
89
|
+
*Special formats:*
|
|
90
|
+
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
91
|
+
- use yaml-rails-ns to have the namespace in the resulting yaml as first key scope
|
|
88
92
|
|
|
89
93
|
```sh
|
|
90
94
|
locize download --project-id my-project-id-93e1-442a-ab35-24331fa294ba --ver latest --language en --namespace namespace1 --path ./backup --format android
|
|
@@ -133,7 +137,11 @@ Add your api-key and your project-id and let's go...
|
|
|
133
137
|
locize sync --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
134
138
|
```
|
|
135
139
|
|
|
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)
|
|
140
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
141
|
+
|
|
142
|
+
*Special formats:*
|
|
143
|
+
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
144
|
+
- use yaml-rails-ns to have the namespace in the resulting yaml as first key scope
|
|
137
145
|
|
|
138
146
|
```sh
|
|
139
147
|
locize sync --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba --format android
|
|
@@ -158,9 +166,10 @@ Navigate to your locize project and check the results => [www.locize.app](https:
|
|
|
158
166
|
|
|
159
167
|
## Push missing keys to locize from your repository (or any other local directory)
|
|
160
168
|
This is useful, when i.e. using [i18next-scanner](https://github.com/i18next/i18next-scanner), like described [here](https://github.com/locize/i18next-locize-backend/issues/315#issuecomment-586967039).
|
|
161
|
-
The save-missing command uses the [missing API](https://www.locize.com/docs/api#missing-translations) and the sync command uses the [update API](https://www.locize.com/docs/api#update-remove-translations)
|
|
169
|
+
The save-missing command uses the [missing API](https://www.locize.com/docs/api#missing-translations) and the sync command uses the [update API](https://www.locize.com/docs/api#update-remove-translations).
|
|
162
170
|
So, if you want to save new keys (that does not exist in locize), the save-missing command is the better choice.
|
|
163
171
|
Doing so, you can then for example make use of the “created by missing API" filter in the locize UI.
|
|
172
|
+
Also, using this save-missing command does not generate extra modification costs.
|
|
164
173
|
|
|
165
174
|
But if you need to update existing keys, the sync command is the correct choice.
|
|
166
175
|
|
|
@@ -192,7 +201,7 @@ Add your api-key and your project-id and let's go...
|
|
|
192
201
|
locize save-missing --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
193
202
|
```
|
|
194
203
|
|
|
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)
|
|
204
|
+
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
196
205
|
|
|
197
206
|
```sh
|
|
198
207
|
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, xcstrings])', 'json')
|
|
235
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, 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, xcstrings])', 'json')
|
|
364
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, 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, xcstrings])', 'json')
|
|
475
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, 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, xcstrings])', 'json')
|
|
698
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, 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})`)
|
|
@@ -161,11 +161,21 @@ const convertToDesiredFormat = (
|
|
|
161
161
|
) {
|
|
162
162
|
if (isEmpty) return cb(null, '');
|
|
163
163
|
var newData = {};
|
|
164
|
-
newData[lng] =
|
|
165
|
-
newData[lng][namespace] = shouldUnflatten(data) ? unflatten(data) : data;
|
|
164
|
+
newData[lng] = shouldUnflatten(data) ? unflatten(data) : data;
|
|
166
165
|
cb(null, jsyaml.dump(removeUndefinedFromArrays(newData)));
|
|
167
166
|
return;
|
|
168
167
|
}
|
|
168
|
+
if (
|
|
169
|
+
opt.format === 'yaml-rails-ns' ||
|
|
170
|
+
opt.format === 'yml-rails-ns'
|
|
171
|
+
) {
|
|
172
|
+
if (isEmpty) return cb(null, '');
|
|
173
|
+
var newDataNs = {};
|
|
174
|
+
newDataNs[lng] = {};
|
|
175
|
+
newDataNs[lng][namespace] = shouldUnflatten(data) ? unflatten(data) : data;
|
|
176
|
+
cb(null, jsyaml.dump(removeUndefinedFromArrays(newDataNs)));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
169
179
|
if (opt.format === 'android') {
|
|
170
180
|
js2asr(flatten(data), cb);
|
|
171
181
|
return;
|
package/convertToFlatFormat.js
CHANGED
|
@@ -141,13 +141,28 @@ const convertToFlatFormat = (opt, data, lng, cb) => {
|
|
|
141
141
|
opt.format === 'yml-rails'
|
|
142
142
|
) {
|
|
143
143
|
const d = data.toString();
|
|
144
|
-
if (!d || d === '') return cb(null, {});
|
|
144
|
+
if (!d || d.trim() === '') return cb(null, {});
|
|
145
145
|
const jsObj = jsyaml.load(d);
|
|
146
146
|
cb(
|
|
147
147
|
null,
|
|
148
148
|
flatten(
|
|
149
|
-
jsObj[Object.keys(jsObj)[0]]
|
|
150
|
-
|
|
149
|
+
jsObj[Object.keys(jsObj)[0]]
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (
|
|
155
|
+
opt.format === 'yaml-rails-ns' ||
|
|
156
|
+
opt.format === 'yml-rails-ns'
|
|
157
|
+
) {
|
|
158
|
+
const dn = data.toString();
|
|
159
|
+
if (!dn || dn.trim() === '') return cb(null, {});
|
|
160
|
+
const jsObjn = jsyaml.load(dn);
|
|
161
|
+
cb(
|
|
162
|
+
null,
|
|
163
|
+
flatten(
|
|
164
|
+
jsObjn[Object.keys(jsObjn)[0]][
|
|
165
|
+
Object.keys(jsObjn[Object.keys(jsObjn)[0]])[0]
|
|
151
166
|
]
|
|
152
167
|
)
|
|
153
168
|
);
|
package/download.js
CHANGED
|
@@ -94,9 +94,10 @@ function handleDownload(opt, url, err, res, downloads, cb) {
|
|
|
94
94
|
if (clb) clb(null);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
97
98
|
if (!opt.version) {
|
|
98
99
|
if (mkdirPath) mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
99
|
-
fs.writeFile(path.join(opt.path, version, filledMask),
|
|
100
|
+
fs.writeFile(path.join(opt.path, version, filledMask), fileContent, logAndClb);
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -145,21 +146,22 @@ function handleDownload(opt, url, err, res, downloads, cb) {
|
|
|
145
146
|
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
146
147
|
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
147
148
|
}
|
|
149
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
148
150
|
if (!opt.version) {
|
|
149
151
|
if (mkdirPath) mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
150
|
-
fs.writeFile(path.join(opt.path, version, filledMask),
|
|
152
|
+
fs.writeFile(path.join(opt.path, version, filledMask), fileContent, clb);
|
|
151
153
|
return;
|
|
152
154
|
}
|
|
153
155
|
if (!opt.language) {
|
|
154
156
|
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
155
|
-
fs.writeFile(path.join(opt.path, filledMask),
|
|
157
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
if (filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + language, '');
|
|
160
162
|
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
161
163
|
mkdirp.sync(parentDir);
|
|
162
|
-
fs.writeFile(path.join(opt.path, filledMask),
|
|
164
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
163
165
|
});
|
|
164
166
|
});
|
|
165
167
|
}, (err) => {
|
|
@@ -231,7 +233,8 @@ function handlePull(opt, toDownload, cb) {
|
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
234
|
-
|
|
236
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
237
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, logAndClb);
|
|
235
238
|
} catch (e) {
|
|
236
239
|
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
237
240
|
return clb(err);
|
|
@@ -274,16 +277,17 @@ function handlePull(opt, toDownload, cb) {
|
|
|
274
277
|
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
275
278
|
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
276
279
|
}
|
|
280
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
277
281
|
if (!opt.language) {
|
|
278
282
|
if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
279
|
-
fs.writeFile(path.join(opt.path, filledMask),
|
|
283
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
280
284
|
return;
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
if (filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + lng, '');
|
|
284
288
|
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
285
289
|
mkdirp.sync(parentDir);
|
|
286
|
-
fs.writeFile(path.join(opt.path, filledMask),
|
|
290
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
287
291
|
});
|
|
288
292
|
});
|
|
289
293
|
}, (err) => {
|
package/format.js
CHANGED
|
@@ -113,7 +113,9 @@ function writeLocalFile(opt, file, clb) {
|
|
|
113
113
|
return clb(null, true);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
const fileContent = opt.format !== 'xlsx' ? (file.converted + '\n') : file.converted;
|
|
117
|
+
|
|
118
|
+
fs.writeFile(file.path, fileContent, (err) => clb(err, true));
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
function writeLocalFiles(opt, files, clb) {
|
package/formats.js
CHANGED
|
@@ -5,8 +5,8 @@ const fileExtensionsMap = {
|
|
|
5
5
|
'.strings': ['strings'],
|
|
6
6
|
'.csv': ['csv'],
|
|
7
7
|
'.resx': ['resx'],
|
|
8
|
-
'.yaml': ['yaml', 'yaml-rails', 'yaml-nested'],
|
|
9
|
-
'.yml': ['yml', 'yml-rails', 'yml-nested'],
|
|
8
|
+
'.yaml': ['yaml', 'yaml-rails', 'yaml-rails-ns', 'yaml-nested'],
|
|
9
|
+
'.yml': ['yml', 'yml-rails', 'yml-rails-ns', 'yml-nested'],
|
|
10
10
|
'.xlsx': ['xlsx'],
|
|
11
11
|
'.xliff': ['xliff2', 'xliff12'],
|
|
12
12
|
'.xlf': ['xlf2', 'xlf12'],
|
package/package.json
CHANGED
package/sync.js
CHANGED
|
@@ -268,7 +268,8 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
|
|
|
268
268
|
}
|
|
269
269
|
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
270
270
|
mkdirp.sync(parentDir);
|
|
271
|
-
|
|
271
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
272
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
272
273
|
} catch (e) {
|
|
273
274
|
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
274
275
|
return clb(err);
|
|
@@ -307,7 +308,8 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
|
|
|
307
308
|
}
|
|
308
309
|
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
309
310
|
mkdirp.sync(parentDir);
|
|
310
|
-
|
|
311
|
+
const fileContent = opt.format !== 'xlsx' ? (converted + '\n') : converted;
|
|
312
|
+
fs.writeFile(path.join(opt.path, filledMask), fileContent, clb);
|
|
311
313
|
});
|
|
312
314
|
});
|
|
313
315
|
}, cb);
|
|
@@ -487,7 +489,9 @@ const backupDeleted = (opt, ns, now) => {
|
|
|
487
489
|
return prev;
|
|
488
490
|
}, {});
|
|
489
491
|
mkdirp.sync(path.join(currentBackupPath, ns.language));
|
|
490
|
-
|
|
492
|
+
const content = JSON.stringify(removingRemote, null, 2);
|
|
493
|
+
const fileContent = opt.format !== 'xlsx' ? (content + '\n') : content;
|
|
494
|
+
fs.writeFileSync(path.join(currentBackupPath, ns.language, `${ns.namespace}.json`), fileContent);
|
|
491
495
|
};
|
|
492
496
|
|
|
493
497
|
const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
|