apostrophe 3.11.0 → 3.12.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 +23 -0
- package/index.js +37 -1
- package/lib/moog.js +2 -2
- package/modules/@apostrophecms/doc/index.js +2 -2
- package/modules/@apostrophecms/i18n/index.js +59 -12
- package/modules/@apostrophecms/module/index.js +12 -2
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/piece-type/index.js +1 -1
- package/modules/@apostrophecms/schema/index.js +18 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +12 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +4 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +17 -0
- package/modules/@apostrophecms/util/index.js +3 -9
- package/package.json +1 -1
- package/test/modules/base-type/i18n/custom/en.json +4 -0
- package/test/modules/base-type/i18n/en.json +3 -0
- package/test/modules/nested-module-subdirs/example1/index.js +5 -0
- package/test/modules/nested-module-subdirs/modules.js +7 -0
- package/test/modules/subtype/i18n/custom/en.json +4 -0
- package/test/modules/subtype/index.js +7 -0
- package/test/static-i18n.js +28 -0
- package/test/with-nested-module-subdirs.js +32 -0
- package/test/without-nested-module-subdirs.js +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.12.0 - 2022-01-21
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
|
|
8
|
+
* Brought back the `nestedModuleSubdirs` feature from A2, which allows modules to be nested in subdirectories if `nestedModuleSubdirs: true` is set in `app.js`. As in A2, module configuration (including activation) can also be grouped in a `modules.js` file in such subdirectories.
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
|
|
12
|
+
* Fixes minor inline documentation comments.
|
|
13
|
+
* UI strings that are not registered localization keys will now display properly when they contain a colon (`:`). These were previously interpreted as i18next namespace/key pairs and the "namespace" portion was left out.
|
|
14
|
+
* Fixes a bug where changing the page type immediately after clicking "New Page" would produce a console error. In general, areas and checkboxes now correctly handle their value being changed to `null` by the parent schema after initial startup of the `AposInputArea` or `AposInputCheckboxes` component.
|
|
15
|
+
* It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
|
|
16
|
+
* Removes the `@apostrophecms/util` module template helper `indexBy`, which was using a lodash method not included in lodash v4.
|
|
17
|
+
* Removes an unimplemented `csrfExceptions` module section cascade. Use the `csrfExceptions` *option* of any module to set an array of URLs excluded from CSRF protection. More information is forthcoming in the documentation.
|
|
18
|
+
* Fix `[Object Object]` in the console when warning `A permission.can() call was made with a type that has no manager` is printed.
|
|
19
|
+
|
|
20
|
+
### Changes
|
|
21
|
+
|
|
22
|
+
* Temporarily removes `npm audit` from our automated tests because of a sub-dependency of vue-loader that doesn't actually cause a security vulnerability for apostrophe.
|
|
23
|
+
|
|
3
24
|
## 3.11.0 - 2022-01-06
|
|
4
25
|
|
|
5
26
|
### Adds
|
|
@@ -9,6 +30,7 @@
|
|
|
9
30
|
### Fixes
|
|
10
31
|
|
|
11
32
|
* Apostrophe's extension of `req.login` now accounts for the `req.logIn` alias and the skippable `options` parameter, which is relied upon in some `passport` strategies.
|
|
33
|
+
* Apostrophe now warns if a nonexistent widget type is configured for an area field, with special attention to when `-widget` has been erroneously included in the name. For backwards compatibility this is a startup warning rather than a fatal error, as sites generally did operate successfully otherwise with this type of bug present.
|
|
12
34
|
|
|
13
35
|
### Changes
|
|
14
36
|
|
|
@@ -16,6 +38,7 @@
|
|
|
16
38
|
* Adds deprecation note to `__testDefaults` option. It is not in use, but removing would be a minor BC break we don't need to make.
|
|
17
39
|
* Allows test modules to use a custom port as an option on the `@apostrophecms/express` module.
|
|
18
40
|
* Removes the code base pull request template to instead inherit the organization-level template.
|
|
41
|
+
* Adds `npm audit` back to the test scripts.
|
|
19
42
|
|
|
20
43
|
## 3.10.0 - 2021-12-22
|
|
21
44
|
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const cluster = require('cluster');
|
|
|
7
7
|
const { cpus } = require('os');
|
|
8
8
|
const process = require('process');
|
|
9
9
|
const npmResolve = require('resolve');
|
|
10
|
+
const glob = require('glob');
|
|
10
11
|
|
|
11
12
|
let defaults = require('./defaults.js');
|
|
12
13
|
|
|
@@ -273,6 +274,33 @@ module.exports = async function(options) {
|
|
|
273
274
|
return _module;
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
function nestedModuleSubdirs() {
|
|
278
|
+
if (!options.nestedModuleSubdirs) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const configs = glob.sync(self.localModules + '/**/modules.js');
|
|
282
|
+
_.each(configs, function(config) {
|
|
283
|
+
try {
|
|
284
|
+
_.merge(self.options.modules, require(config));
|
|
285
|
+
} catch (e) {
|
|
286
|
+
console.error(stripIndent`
|
|
287
|
+
When nestedModuleSubdirs is active, any modules.js file beneath:
|
|
288
|
+
|
|
289
|
+
${self.localModules}
|
|
290
|
+
|
|
291
|
+
must export an object containing configuration for Apostrophe modules.
|
|
292
|
+
|
|
293
|
+
The file:
|
|
294
|
+
|
|
295
|
+
${config}
|
|
296
|
+
|
|
297
|
+
did not parse.
|
|
298
|
+
`);
|
|
299
|
+
throw e;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
276
304
|
function autodetectBundles() {
|
|
277
305
|
const modules = _.keys(self.options.modules);
|
|
278
306
|
_.each(modules, function(name) {
|
|
@@ -406,7 +434,13 @@ module.exports = async function(options) {
|
|
|
406
434
|
localModules: self.localModules,
|
|
407
435
|
defaultBaseClass: '@apostrophecms/module',
|
|
408
436
|
sections: [ 'helpers', 'handlers', 'routes', 'apiRoutes', 'restApiRoutes', 'renderRoutes', 'middleware', 'customTags', 'components', 'tasks' ],
|
|
409
|
-
|
|
437
|
+
nestedModuleSubdirs: self.options.nestedModuleSubdirs,
|
|
438
|
+
unparsedSections: [
|
|
439
|
+
'queries',
|
|
440
|
+
'extendQueries',
|
|
441
|
+
'icons',
|
|
442
|
+
'i18n'
|
|
443
|
+
]
|
|
410
444
|
});
|
|
411
445
|
|
|
412
446
|
self.synth = synth;
|
|
@@ -417,6 +451,8 @@ module.exports = async function(options) {
|
|
|
417
451
|
self.redefine = self.synth.redefine;
|
|
418
452
|
self.create = self.synth.create;
|
|
419
453
|
|
|
454
|
+
nestedModuleSubdirs();
|
|
455
|
+
|
|
420
456
|
_.each(self.options.modules, function(options, name) {
|
|
421
457
|
synth.define(name, options);
|
|
422
458
|
});
|
package/lib/moog.js
CHANGED
|
@@ -369,9 +369,9 @@ module.exports = function(options) {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
function capture(section) {
|
|
372
|
-
that[section] = {};
|
|
372
|
+
that.__meta[section] = {};
|
|
373
373
|
for (const step of steps) {
|
|
374
|
-
that[section][step.__meta.name] = step[section];
|
|
374
|
+
that.__meta[section][step.__meta.name] = step[section];
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
|
|
@@ -675,10 +675,10 @@ module.exports = {
|
|
|
675
675
|
},
|
|
676
676
|
|
|
677
677
|
// Insert the given document. Called by `.insert()`. You will usually want to
|
|
678
|
-
// call the
|
|
678
|
+
// call the insert method of the appropriate doc type manager instead:
|
|
679
679
|
//
|
|
680
680
|
// ```javascript
|
|
681
|
-
// self.apos.doc.getManager(doc.type).
|
|
681
|
+
// self.apos.doc.getManager(doc.type).insert(...)
|
|
682
682
|
// ```
|
|
683
683
|
//
|
|
684
684
|
// However you can override this method to alter the
|
|
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
const _ = require('lodash');
|
|
11
11
|
const { stripIndent } = require('common-tags');
|
|
12
12
|
const ExpressSessionCookie = require('express-session/session/cookie');
|
|
13
|
+
const path = require('path');
|
|
13
14
|
|
|
14
15
|
const apostropheI18nDebugPlugin = {
|
|
15
16
|
type: 'postProcessor',
|
|
@@ -380,21 +381,65 @@ module.exports = {
|
|
|
380
381
|
// Add the i18next resources provided by the specified module,
|
|
381
382
|
// merging with any existing phrases for the same locales and namespaces
|
|
382
383
|
addResourcesForModule(module) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
self.addDefaultResourcesForModule(module);
|
|
385
|
+
self.addNamespacedResourcesForModule(module);
|
|
386
|
+
},
|
|
387
|
+
// Automatically adds any localizations found in .json files in the main `i18n` subdirectory
|
|
388
|
+
// of a module.
|
|
389
|
+
//
|
|
390
|
+
// These are added to the `default` namespace, unless the legacy `i18n.ns` option is set
|
|
391
|
+
// for the module (not the preferred way, use namespace subdirectories in new projects).
|
|
392
|
+
addDefaultResourcesForModule(module) {
|
|
393
|
+
const ns = (module.options.i18n && module.options.i18n.ns) || 'default';
|
|
387
394
|
self.namespaces[ns] = self.namespaces[ns] || {};
|
|
388
|
-
self.namespaces[ns].browser = self.namespaces[ns].browser ||
|
|
395
|
+
self.namespaces[ns].browser = self.namespaces[ns].browser || (module.options.i18n && module.options.i18n.browser);
|
|
389
396
|
for (const entry of module.__meta.chain) {
|
|
390
|
-
const localizationsDir =
|
|
391
|
-
if (!
|
|
392
|
-
|
|
397
|
+
const localizationsDir = path.join(entry.dirname, 'i18n');
|
|
398
|
+
if (!self.defaultLocalizationsDirsAdded.has(localizationsDir)) {
|
|
399
|
+
self.defaultLocalizationsDirsAdded.add(localizationsDir);
|
|
400
|
+
if (!fs.existsSync(localizationsDir)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
for (const localizationFile of fs.readdirSync(localizationsDir)) {
|
|
404
|
+
if (!localizationFile.endsWith('.json')) {
|
|
405
|
+
// Likely a namespace subdirectory
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const data = JSON.parse(fs.readFileSync(path.join(localizationsDir, localizationFile)));
|
|
409
|
+
const locale = localizationFile.replace('.json', '');
|
|
410
|
+
self.i18next.addResourceBundle(locale, ns, data, true, true);
|
|
411
|
+
}
|
|
393
412
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
// Automatically adds any localizations found in subdirectories of the main `i18n`
|
|
416
|
+
// subdirectory of a module. The subdirectory's name is treated as an i18n namespace
|
|
417
|
+
// name.
|
|
418
|
+
addNamespacedResourcesForModule(module) {
|
|
419
|
+
for (const entry of module.__meta.chain) {
|
|
420
|
+
const metadata = module.__meta.i18n[entry.name] || {};
|
|
421
|
+
const localizationsDir = `${entry.dirname}/i18n`;
|
|
422
|
+
if (!self.namespacedLocalizationsDirsAdded.has(localizationsDir)) {
|
|
423
|
+
self.namespacedLocalizationsDirsAdded.add(localizationsDir);
|
|
424
|
+
if (!fs.existsSync(localizationsDir)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
for (const ns of fs.readdirSync(localizationsDir)) {
|
|
428
|
+
if (ns.endsWith('.json')) {
|
|
429
|
+
// A JSON file for the default namespace, already handled
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
self.namespaces[ns] = self.namespaces[ns] || {};
|
|
433
|
+
self.namespaces[ns].browser = self.namespaces[ns].browser ||
|
|
434
|
+
(metadata[ns] && metadata[ns].browser);
|
|
435
|
+
const namespaceDir = path.join(localizationsDir, ns);
|
|
436
|
+
for (const localizationFile of fs.readdirSync(namespaceDir)) {
|
|
437
|
+
const fullLocalizationFile = path.join(namespaceDir, localizationFile);
|
|
438
|
+
const data = JSON.parse(fs.readFileSync(fullLocalizationFile));
|
|
439
|
+
const locale = localizationFile.replace('.json', '');
|
|
440
|
+
self.i18next.addResourceBundle(locale, ns, data, true, true);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
398
443
|
}
|
|
399
444
|
}
|
|
400
445
|
},
|
|
@@ -402,6 +447,8 @@ module.exports = {
|
|
|
402
447
|
// itself, called by init. Later modules call addResourcesForModule(self),
|
|
403
448
|
// making phrases available gradually as Apostrophe starts up
|
|
404
449
|
addInitialResources() {
|
|
450
|
+
self.defaultLocalizationsDirsAdded = new Set();
|
|
451
|
+
self.namespacedLocalizationsDirsAdded = new Set();
|
|
405
452
|
for (const module of Object.values(self.apos.modules)) {
|
|
406
453
|
self.addResourcesForModule(module);
|
|
407
454
|
}
|
|
@@ -29,10 +29,20 @@ const _ = require('lodash');
|
|
|
29
29
|
|
|
30
30
|
module.exports = {
|
|
31
31
|
|
|
32
|
-
cascades: [ 'csrfExceptions' ],
|
|
33
|
-
|
|
34
32
|
init(self) {
|
|
35
33
|
self.apos = self.options.apos;
|
|
34
|
+
const capturedSections = [
|
|
35
|
+
'queries',
|
|
36
|
+
'extendQueries',
|
|
37
|
+
'icons'
|
|
38
|
+
];
|
|
39
|
+
for (const section of capturedSections) {
|
|
40
|
+
// Unparsed sections are now captured in __meta, promote
|
|
41
|
+
// these to the top level to maintain bc. For new unparsed
|
|
42
|
+
// sections we'll leave them in `__meta` to avoid bc breaks
|
|
43
|
+
// with project-level properties of the module
|
|
44
|
+
self[section] = self.__meta[section];
|
|
45
|
+
}
|
|
36
46
|
// all apostrophe modules are properties of self.apos.modules.
|
|
37
47
|
// Those with an alias are also properties of self.apos
|
|
38
48
|
self.apos.modules[self.__meta.name] = self;
|
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
const doc = (docOrType && docOrType._id) ? docOrType : null;
|
|
64
64
|
const manager = type && self.apos.doc.getManager(type);
|
|
65
65
|
if (type && !manager) {
|
|
66
|
-
self.apos.util.warn(
|
|
66
|
+
self.apos.util.warn('A permission.can() call was made with a type that has no manager:', type);
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
if (action === 'view') {
|
|
@@ -566,7 +566,7 @@ module.exports = {
|
|
|
566
566
|
},
|
|
567
567
|
//
|
|
568
568
|
// Update a piece. Convenience wrapper for `apos.doc.insert`.
|
|
569
|
-
// Returns the piece. `
|
|
569
|
+
// Returns the piece. `beforeUpdate`, `beforeSave`, `afterUpdate`
|
|
570
570
|
// and `afterSave` async events are emitted by this module.
|
|
571
571
|
async update(req, piece, options) {
|
|
572
572
|
return self.apos.doc.update(req, piece, options);
|
|
@@ -18,7 +18,7 @@ const _ = require('lodash');
|
|
|
18
18
|
const dayjs = require('dayjs');
|
|
19
19
|
const tinycolor = require('tinycolor2');
|
|
20
20
|
const { klona } = require('klona');
|
|
21
|
-
const {
|
|
21
|
+
const { stripIndents } = require('common-tags');
|
|
22
22
|
|
|
23
23
|
module.exports = {
|
|
24
24
|
options: {
|
|
@@ -74,6 +74,22 @@ module.exports = {
|
|
|
74
74
|
return true;
|
|
75
75
|
}
|
|
76
76
|
return _.isEqual(one[field.name], two[field.name]);
|
|
77
|
+
},
|
|
78
|
+
validate: function (field, options, warn, fail) {
|
|
79
|
+
if (field.options && field.options.widgets) {
|
|
80
|
+
for (const name of Object.keys(field.options.widgets)) {
|
|
81
|
+
if (!self.apos.modules[`${name}-widget`]) {
|
|
82
|
+
if (name.match(/-widget$/)) {
|
|
83
|
+
warn(stripIndents`
|
|
84
|
+
Do not include "-widget" in the name when configuring a widget in an area field.
|
|
85
|
+
Apostrophe will automatically add "-widget" when looking for the right module.
|
|
86
|
+
`);
|
|
87
|
+
} else {
|
|
88
|
+
warn(`Nonexistent widget type name ${name} in area field.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
77
93
|
}
|
|
78
94
|
});
|
|
79
95
|
|
|
@@ -2247,7 +2263,7 @@ module.exports = {
|
|
|
2247
2263
|
self.apos.util.error(format(s));
|
|
2248
2264
|
}
|
|
2249
2265
|
function format(s) {
|
|
2250
|
-
return
|
|
2266
|
+
return stripIndents`
|
|
2251
2267
|
${options.type} ${options.subtype}, ${field.type} field "${field.name}":
|
|
2252
2268
|
|
|
2253
2269
|
${s}
|
|
@@ -39,11 +39,7 @@ export default {
|
|
|
39
39
|
mixins: [ AposInputMixin ],
|
|
40
40
|
data () {
|
|
41
41
|
return {
|
|
42
|
-
next: this.value.data ||
|
|
43
|
-
metaType: 'area',
|
|
44
|
-
_id: cuid(),
|
|
45
|
-
items: []
|
|
46
|
-
},
|
|
42
|
+
next: this.value.data || this.getEmptyValue(),
|
|
47
43
|
error: false,
|
|
48
44
|
// This is just meant to be sufficient to prevent unintended collisions
|
|
49
45
|
// in the UI between id attributes
|
|
@@ -66,6 +62,17 @@ export default {
|
|
|
66
62
|
}
|
|
67
63
|
},
|
|
68
64
|
methods: {
|
|
65
|
+
getEmptyValue() {
|
|
66
|
+
return {
|
|
67
|
+
metaType: 'area',
|
|
68
|
+
_id: cuid(),
|
|
69
|
+
items: []
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
watchValue () {
|
|
73
|
+
this.error = this.value.error;
|
|
74
|
+
this.next = this.value.data || this.getEmptyValue();
|
|
75
|
+
},
|
|
69
76
|
validate(value) {
|
|
70
77
|
if (this.field.required) {
|
|
71
78
|
if (!value.items.length) {
|
|
@@ -32,6 +32,10 @@ export default {
|
|
|
32
32
|
getChoiceId(uid, value) {
|
|
33
33
|
return uid + value.replace(/\s/g, '');
|
|
34
34
|
},
|
|
35
|
+
watchValue () {
|
|
36
|
+
this.error = this.value.error;
|
|
37
|
+
this.next = this.value.data || [];
|
|
38
|
+
},
|
|
35
39
|
validate(values) {
|
|
36
40
|
// The choices and values should always be arrays.
|
|
37
41
|
if (!Array.isArray(this.field.choices) || !Array.isArray(values)) {
|
|
@@ -23,6 +23,23 @@ export default {
|
|
|
23
23
|
debug: i18n.debug,
|
|
24
24
|
interpolation: {
|
|
25
25
|
escapeValue: false
|
|
26
|
+
},
|
|
27
|
+
appendNamespaceToMissingKey: true,
|
|
28
|
+
parseMissingKeyHandler (key) {
|
|
29
|
+
// We include namespaces with unrecognized l10n keys using
|
|
30
|
+
// `appendNamespaceToMissingKey: true`. This passes strings containing
|
|
31
|
+
// colons that were never meant to be localized through to the UI.
|
|
32
|
+
//
|
|
33
|
+
// Strings that do not include colons ("Content area") are given the
|
|
34
|
+
// default namespace by i18next ("translation," by default). Here we
|
|
35
|
+
// check if the key starts with that default namespace, meaning it
|
|
36
|
+
// belongs to no other registered namespace, then remove that default
|
|
37
|
+
// namespace before passing this through to be processed and displayed.
|
|
38
|
+
if (key.startsWith(`${this.defaultNS[0]}:`)) {
|
|
39
|
+
return key.slice(this.defaultNS[0].length + 1);
|
|
40
|
+
} else {
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
});
|
|
28
45
|
|
|
@@ -936,15 +936,6 @@ module.exports = {
|
|
|
936
936
|
});
|
|
937
937
|
},
|
|
938
938
|
|
|
939
|
-
// If propertyName is _id, then the keys in the returned
|
|
940
|
-
// object will be the ids of each object in arr,
|
|
941
|
-
// and the values will be the corresponding objects.
|
|
942
|
-
// You may index by any property name.
|
|
943
|
-
|
|
944
|
-
indexBy: function(arr, propertyName) {
|
|
945
|
-
return _.indexBy(arr, propertyName);
|
|
946
|
-
},
|
|
947
|
-
|
|
948
939
|
// Find all the array elements, if any, that have the specified value for
|
|
949
940
|
// the specified property.
|
|
950
941
|
|
|
@@ -1061,11 +1052,14 @@ module.exports = {
|
|
|
1061
1052
|
|
|
1062
1053
|
function groupByArray(items, arrayName) {
|
|
1063
1054
|
const results = {};
|
|
1055
|
+
// looping over each item in the original array
|
|
1064
1056
|
_.each(items, function(item) {
|
|
1057
|
+
// looping over each item in the array within the top level item
|
|
1065
1058
|
_.each(item[arrayName] || [], function(inner) {
|
|
1066
1059
|
if (!results[inner]) {
|
|
1067
1060
|
results[inner] = [];
|
|
1068
1061
|
}
|
|
1062
|
+
// grouping top level items on the sub properties
|
|
1069
1063
|
results[inner].push(item);
|
|
1070
1064
|
});
|
|
1071
1065
|
});
|
package/package.json
CHANGED
package/test/static-i18n.js
CHANGED
|
@@ -33,9 +33,19 @@ describe('static i18n', function() {
|
|
|
33
33
|
'apos-fr': {
|
|
34
34
|
options: {
|
|
35
35
|
i18n: {
|
|
36
|
+
// Legacy technique must work
|
|
36
37
|
ns: 'apostrophe'
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
},
|
|
41
|
+
// A base class that contributes some namespaced phrases in the new style way (subdirs)
|
|
42
|
+
'base-type': {
|
|
43
|
+
instantiate: false
|
|
44
|
+
},
|
|
45
|
+
// Also contributes namespaced phrases in the new style way (subdirs)
|
|
46
|
+
// plus default locale phrases in the root i18n folder
|
|
47
|
+
subtype: {
|
|
48
|
+
extend: 'base-type'
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
});
|
|
@@ -60,4 +70,22 @@ describe('static i18n', function() {
|
|
|
60
70
|
assert.strictEqual(apos.task.getReq({ locale: 'fr' }).t('apostrophe:richTextAlignCenter'), 'Aligner Le Centre');
|
|
61
71
|
});
|
|
62
72
|
|
|
73
|
+
it('should fetch default locale phrases from main i18n dir with no i18n option necessary', function() {
|
|
74
|
+
assert.strictEqual(apos.task.getReq().t('defaultTestOne'), 'Default Test One');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should fetch custom locale phrases from corresponding subdir', function() {
|
|
78
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestTwo'), 'Custom Test Two From Base Type');
|
|
79
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestThree'), 'Custom Test Three From Subtype');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('last appearance in inheritance + configuration order wins', function() {
|
|
83
|
+
assert.strictEqual(apos.task.getReq().t('custom:customTestOne'), 'Custom Test One From Subtype');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should honor the browser: true flag in the i18n section of an index.js file', function() {
|
|
87
|
+
const browserData = apos.i18n.getBrowserData(apos.task.getReq());
|
|
88
|
+
assert.strictEqual(browserData.i18n.en.custom.customTestOne, 'Custom Test One From Subtype');
|
|
89
|
+
});
|
|
90
|
+
|
|
63
91
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('With Nested Module Subdirs', function() {
|
|
5
|
+
this.timeout(t.timeout);
|
|
6
|
+
|
|
7
|
+
let apos;
|
|
8
|
+
|
|
9
|
+
after(function () {
|
|
10
|
+
return t.destroy(apos);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/// ///
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
/// ///
|
|
16
|
+
|
|
17
|
+
it('should initialize', async function() {
|
|
18
|
+
apos = await t.create({
|
|
19
|
+
root: module,
|
|
20
|
+
nestedModuleSubdirs: true,
|
|
21
|
+
modules: {
|
|
22
|
+
example1: {}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
assert(apos.modules.example1);
|
|
26
|
+
// With nestedModuleSubdirs switched on, the index.js should be found,
|
|
27
|
+
// and modules.js should be loaded
|
|
28
|
+
assert(apos.modules.example1.options.folderLevelOption);
|
|
29
|
+
assert(apos.modules.example1.initialized);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('Without Nested Module Subdirs', function() {
|
|
5
|
+
this.timeout(t.timeout);
|
|
6
|
+
|
|
7
|
+
let apos;
|
|
8
|
+
|
|
9
|
+
after(function () {
|
|
10
|
+
return t.destroy(apos);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/// ///
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
/// ///
|
|
16
|
+
|
|
17
|
+
it('should initialize', async function() {
|
|
18
|
+
apos = await t.create({
|
|
19
|
+
root: module,
|
|
20
|
+
modules: {
|
|
21
|
+
example1: {}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
assert(apos.modules.example1);
|
|
25
|
+
// Should fail because we didn't turn on nestedModuleSubdirs,
|
|
26
|
+
// so the index.js was not found and modules.js was not loaded
|
|
27
|
+
assert(!apos.modules.example1.options.folderLevelOption);
|
|
28
|
+
assert(!apos.modules.example1.initialized);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
});
|