@sveltekit-i18n/base 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/dist/I18n.svelte.d.ts +4 -3
- package/dist/I18n.svelte.js +54 -27
- package/dist/types.d.ts +10 -7
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +36 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,8 +122,8 @@ export { load } from '$lib/i18n';
|
|
|
122
122
|
The server picks the visitor's locale from the `Accept-Language` header (or
|
|
123
123
|
from a cookie, with `preferredLocale`), loads it per request and hands it to
|
|
124
124
|
the browser, so nothing loads twice and no visitor sees another's locale. See
|
|
125
|
-
[SvelteKit](
|
|
126
|
-
[Server-Side Rendering](
|
|
125
|
+
[SvelteKit](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#sveltekit) for the details and
|
|
126
|
+
[Server-Side Rendering](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#server-side-rendering) for wiring it
|
|
127
127
|
by hand.
|
|
128
128
|
|
|
129
129
|
### 4. Use in components
|
|
@@ -232,11 +232,11 @@ A named capture group in a `RegExp` route is a load parameter: its match reaches
|
|
|
232
232
|
}
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
`API_ORIGIN` stands for an origin such as `https://api.example.com`: a loader runs on the server too, where `fetch` takes only an absolute URL. See [route params](
|
|
235
|
+
`API_ORIGIN` stands for an origin such as `https://api.example.com`: a loader runs on the server too, where `fetch` takes only an absolute URL. See [route params](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#route-params) for the rules.
|
|
236
236
|
|
|
237
|
-
A loader whose source does the caching itself — a SvelteKit remote `query`, an SWR layer, an HTTP cache — sets `cache: false`. It then runs on every load trigger that selects it, and `config.cache` does not apply to it; only a hydrated snapshot holds it back, for the locale and route it was rendered for. See [the loader's `cache`](
|
|
237
|
+
A loader whose source does the caching itself — a SvelteKit remote `query`, an SWR layer, an HTTP cache — sets `cache: false`. It then runs on every load trigger that selects it, and `config.cache` does not apply to it; only a hydrated snapshot holds it back, for the locale and route it was rendered for. See [the loader's `cache`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#cache-optional).
|
|
238
238
|
|
|
239
|
-
A loader that throws is logged, and the rest of the load lands without its data; it runs again on the next load trigger. SvelteKit's `redirect()` and an `error()` below 500 (told by their shape: an own `status` with a `location` or a `body`, on a value that is not an `Error`) are logged too, but they also reject the load, so a SvelteKit `load` awaiting the call hands them to SvelteKit; the rejected call is undone — what it replaced goes back — unless a later call that has not failed came in the meantime. See [the loader](
|
|
239
|
+
A loader that throws is logged, and the rest of the load lands without its data; it runs again on the next load trigger. SvelteKit's `redirect()` and an `error()` below 500 (told by their shape: an own `status` with a `location` or a `body`, on a value that is not an `Error`) are logged too, but they also reject the load, so a SvelteKit `load` awaiting the call hands them to SvelteKit; the rejected call is undone — what it replaced goes back — unless a later call that has not failed came in the meantime. See [the loader](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#loader-required).
|
|
240
240
|
|
|
241
241
|
Both `loaders` and a loader's `routes` accept readonly arrays, so a whole-config `as const` is fine.
|
|
242
242
|
|
|
@@ -251,11 +251,11 @@ import { PUBLIC_BASE_PATH } from '$env/static/public';
|
|
|
251
251
|
basePath: PUBLIC_BASE_PATH
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
See [`basePath`](
|
|
254
|
+
See [`basePath`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#basepath).
|
|
255
255
|
|
|
256
256
|
### `translations`
|
|
257
257
|
|
|
258
|
-
Synchronous translations, available immediately. They seed the tables: the loaders of a namespace they name still run and merge into it. Hand a server's state over with [`hydrate()`](
|
|
258
|
+
Synchronous translations, available immediately. They seed the tables: the loaders of a namespace they name still run and merge into it. Hand a server's state over with [`hydrate()`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#hydrateenvelope) instead:
|
|
259
259
|
|
|
260
260
|
```javascript
|
|
261
261
|
translations: {
|
|
@@ -273,7 +273,7 @@ Initialize with a specific locale immediately:
|
|
|
273
273
|
initLocale: 'en'
|
|
274
274
|
```
|
|
275
275
|
|
|
276
|
-
With [`defineI18n()`](#sveltekit) it loads nothing: it is a negotiation candidate. Leave it out of a config whose instance you [`hydrate()`](
|
|
276
|
+
With [`defineI18n()`](#sveltekit) it loads nothing: it is a negotiation candidate. Leave it out of a config whose instance you [`hydrate()`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#hydrateenvelope) by hand — its load starts in the constructor, before the hand-off can be applied.
|
|
277
277
|
|
|
278
278
|
### `fallbackLocale`
|
|
279
279
|
|
|
@@ -295,7 +295,7 @@ fallbackValue: '...' // Default: returns the key itself
|
|
|
295
295
|
|
|
296
296
|
### `sanitizeLocales`
|
|
297
297
|
|
|
298
|
-
How locale identifiers are normalized before they key anything: `true` (default) resolves them to their ISO form through `Intl` (`'en-us'` is `'en-US'`), `false` keeps them as authored, and a function normalizes them your way. See [`sanitizeLocales`](
|
|
298
|
+
How locale identifiers are normalized before they key anything: `true` (default) resolves them to their ISO form through `Intl` (`'en-us'` is `'en-US'`), `false` keeps them as authored, and a function normalizes them your way. See [`sanitizeLocales`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#sanitizelocales).
|
|
299
299
|
|
|
300
300
|
### `preprocess`
|
|
301
301
|
|
|
@@ -334,11 +334,11 @@ Or state it per instance — only its type is read, so the value can stay empty
|
|
|
334
334
|
const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
-
Hand-write it for a small set of messages, or generate it — [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen), a separate package, generates one. A schema whose keys are not a closed set (`schema: {}`) types nothing and keeps the registry out, so keys stay plain strings. A library never registers. The registry needs base 3.1 or newer. See [`schema`](
|
|
337
|
+
Hand-write it for a small set of messages, or generate it — [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen), a separate package, generates one. A schema whose keys are not a closed set (`schema: {}`) types nothing and keeps the registry out, so keys stay plain strings. A library never registers. The registry needs base 3.1 or newer. See [`schema`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#schema) for the full rules.
|
|
338
338
|
|
|
339
339
|
### `cache`
|
|
340
340
|
|
|
341
|
-
Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — each loader (but one with [`cache: false`](
|
|
341
|
+
Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — each loader (but one with [`cache: false`](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#cache-optional)) runs once per locale and [route params](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#route-params) (a loader's `routes` decide whether a load trigger considers it, and the params their named groups capture decide when it runs again).
|
|
342
342
|
|
|
343
343
|
Set a finite value when your loaders fetch from a source that can change at runtime (e.g. a CMS):
|
|
344
344
|
|
|
@@ -399,7 +399,7 @@ log: {
|
|
|
399
399
|
- `locales` – available locales
|
|
400
400
|
- `loading` – `true` while any activating load is in flight; a `{ activate: false }` load counts only once an activating trigger joins it
|
|
401
401
|
- `initialized` – locale and route set, translations present
|
|
402
|
-
- `translations` / `rawTranslations` – the
|
|
402
|
+
- `translations` / `rawTranslations` – the tables after and before preprocessing
|
|
403
403
|
|
|
404
404
|
### Methods
|
|
405
405
|
|
|
@@ -443,12 +443,12 @@ export const { handle, load, use, get } = defineI18n(config, { preferredLocale }
|
|
|
443
443
|
- `use(() => data)` – called once in the root `+layout.svelte`; provides the instance, follows every navigation and keeps `<html lang>` and `<html dir>` in sync
|
|
444
444
|
- `get()` – the instance, in any component below the root layout
|
|
445
445
|
|
|
446
|
-
Full API documentation: [docs/README.md](
|
|
446
|
+
Full API documentation: [docs/README.md](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md)
|
|
447
447
|
|
|
448
448
|
## Documentation
|
|
449
449
|
|
|
450
450
|
- 🌐 [sveltekit-i18n.github.io](https://sveltekit-i18n.github.io) – The documentation site, with a live playground
|
|
451
|
-
- 📖 [Full API Documentation](
|
|
451
|
+
- 📖 [Full API Documentation](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md) – Complete reference
|
|
452
452
|
- 📚 [Main Library Docs](https://github.com/sveltekit-i18n/lib/tree/master/docs/INDEX.md) – Guides, tutorials, and best practices
|
|
453
453
|
- 🎨 [Parsers](https://github.com/sveltekit-i18n/parsers) – Available parsers and how to create your own
|
|
454
454
|
- 💡 [Examples](https://github.com/sveltekit-i18n/lib/tree/master/examples) – Real-world usage examples
|
|
@@ -479,7 +479,7 @@ i18n.setLocale('en'); // 'en' | 'de' autocomplete here
|
|
|
479
479
|
i18n.setLocale('sv'); // still accepted — the union is a hint, not a constraint
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
-
The locales survive only when the config reaches the constructor as a literal — inline, as above, or a separate object with `as const`. An annotated or separately widened config, and any config with one dynamic locale source (`loaders: locales.map(...)`), leaves them plain `string`. See [TypeScript](
|
|
482
|
+
The locales survive only when the config reaches the constructor as a literal — inline, as above, or a separate object with `as const`. An annotated or separately widened config, and any config with one dynamic locale source (`loaders: locales.map(...)`), leaves them plain `string`. See [TypeScript](https://github.com/sveltekit-i18n/base/blob/3.1.2/docs/README.md#typescript) for both.
|
|
483
483
|
|
|
484
484
|
## Related Packages
|
|
485
485
|
|
package/dist/I18n.svelte.d.ts
CHANGED
|
@@ -113,9 +113,10 @@ declare class I18nCore<ParserParams extends Parser.Params = any, ParserOutput =
|
|
|
113
113
|
* records name each loader, so a namespace fed by several loaders is handed
|
|
114
114
|
* over too, and so is one a loader delivered for route params while its
|
|
115
115
|
* record says so — not a namespace with both, whose data the client could
|
|
116
|
-
* not split between them. What was seeded into the namespace of a
|
|
117
|
-
* whose routes capture params travels apart as `seeds`,
|
|
118
|
-
*
|
|
116
|
+
* not split between them. What was seeded into the namespace of a recorded
|
|
117
|
+
* loader, or of one whose routes capture params, travels apart as `seeds`,
|
|
118
|
+
* so it outlives the loader's next fetch there, and reaches the client
|
|
119
|
+
* where the namespace is left out.
|
|
119
120
|
*/
|
|
120
121
|
snapshot: {
|
|
121
122
|
(options?: {
|
package/dist/I18n.svelte.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
|
-
import { capturesParams, fetchTranslation, hasOwn, loaderName, mergeFetched, mergeTranslations, omitProtoKeys, paramsSignature, read, resolveLoaders, routeParams, sanitizerFactory, sanitizeTranslationLocales, serialize, servedLocales, toDotNotation, translate, unique, withoutBasePath } from './utils.js';
|
|
2
|
+
import { capturesParams, fetchTranslation, hasOwn, loaderName, maskOutputKeys, maskTranslations, mergeFetched, mergeTranslations, omitProtoKeys, paramsSignature, read, resolveLoaders, routeParams, sanitizerFactory, sanitizeTranslationLocales, serialize, servedLocales, toDotNotation, translate, unique, withoutBasePath } from './utils.js';
|
|
3
3
|
import { logError, logger, loggerFactory, setLogger } from './logger.js';
|
|
4
4
|
const defaultCache = Number.POSITIVE_INFINITY;
|
|
5
5
|
/**
|
|
@@ -413,9 +413,10 @@ class I18nCore {
|
|
|
413
413
|
* records name each loader, so a namespace fed by several loaders is handed
|
|
414
414
|
* over too, and so is one a loader delivered for route params while its
|
|
415
415
|
* record says so — not a namespace with both, whose data the client could
|
|
416
|
-
* not split between them. What was seeded into the namespace of a
|
|
417
|
-
* whose routes capture params travels apart as `seeds`,
|
|
418
|
-
*
|
|
416
|
+
* not split between them. What was seeded into the namespace of a recorded
|
|
417
|
+
* loader, or of one whose routes capture params, travels apart as `seeds`,
|
|
418
|
+
* so it outlives the loader's next fetch there, and reaches the client
|
|
419
|
+
* where the namespace is left out.
|
|
419
420
|
*/
|
|
420
421
|
snapshot = ((options) => {
|
|
421
422
|
const withRecords = options?.records === true;
|
|
@@ -472,11 +473,12 @@ class I18nCore {
|
|
|
472
473
|
return [];
|
|
473
474
|
return [signature ? { id, signature } : { id }];
|
|
474
475
|
});
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
// into
|
|
478
|
-
//
|
|
479
|
-
const
|
|
476
|
+
// The client takes a recorded namespace whole as its loader's delivery,
|
|
477
|
+
// and loads one left out where params can change itself, so what was
|
|
478
|
+
// seeded into either travels apart: the seed has to outlive the delivery
|
|
479
|
+
// the next fetch replaces there as it does here.
|
|
480
|
+
const recorded = new Set(records.map(({ id }) => id));
|
|
481
|
+
const seeds = omitProtoKeys(this.#externalOf(loaders.filter(({ id, locale, routes }) => omitted.has(locale) && locale !== '__proto__' && (capturesParams(routes) || (id !== null && recorded.has(id))))));
|
|
480
482
|
return {
|
|
481
483
|
translations,
|
|
482
484
|
records,
|
|
@@ -585,34 +587,40 @@ class I18nCore {
|
|
|
585
587
|
});
|
|
586
588
|
const takers = new Map([...byIdTaken, ...byFunction]);
|
|
587
589
|
const orphaned = deliveries.filter((delivery) => !takers.has(delivery));
|
|
588
|
-
this.#deliveries = new Map(
|
|
590
|
+
this.#deliveries = new Map(deliveries.flatMap((delivery) => {
|
|
591
|
+
const taker = takers.get(delivery);
|
|
592
|
+
return taker ? [[taker, { ...delivery, loader: taker }]] : [];
|
|
593
|
+
}));
|
|
589
594
|
this.#keepExternal(serialize(orphaned.map(({ loader, data }) => ({ ...loader, data }))));
|
|
590
595
|
}
|
|
591
596
|
/**
|
|
592
|
-
* Applies what loaders delivered and records them as loaded. A loader
|
|
593
|
-
*
|
|
594
|
-
* the namespace is rebuilt from the data
|
|
595
|
-
*
|
|
596
|
-
* survives and a sibling's part
|
|
597
|
+
* Applies what loaders delivered and records them as loaded. A loader that
|
|
598
|
+
* delivered before, for the same params or for others, replaces the part of
|
|
599
|
+
* its namespace it delivered then: the namespace is rebuilt from the data
|
|
600
|
+
* supplied without a loader and from what each of its loaders last
|
|
601
|
+
* delivered, so no key its source dropped survives and a sibling's part
|
|
602
|
+
* stays in place. The preprocessed table of a
|
|
597
603
|
* locale that lost data is derived again from the raw one, since a custom
|
|
598
604
|
* `preprocess` may have renamed the keys that would have to go. Both tables
|
|
599
605
|
* are computed before anything is written, so a `preprocess` that throws
|
|
600
606
|
* records no loader and the next trigger fetches it again.
|
|
601
607
|
*/
|
|
602
|
-
#applyDeliveries(
|
|
608
|
+
#applyDeliveries(applied) {
|
|
609
|
+
const { loaders = [] } = this.#config ?? {};
|
|
610
|
+
// One load, however its deliveries were gathered: a preload claimed with
|
|
611
|
+
// it comes first, yet the loader declared later still wins.
|
|
612
|
+
const deliveries = [...applied].sort((a, b) => loaders.indexOf(a.loader) - loaders.indexOf(b.loader));
|
|
603
613
|
const replaced = deliveries
|
|
604
|
-
.filter(({ loader
|
|
605
|
-
const previous = this.#deliveries.get(loader);
|
|
606
|
-
return previous !== undefined && previous.signature !== signature;
|
|
607
|
-
})
|
|
614
|
+
.filter(({ loader }) => this.#deliveries.has(loader))
|
|
608
615
|
.map(({ loader }) => loader);
|
|
609
|
-
const delivered = new
|
|
616
|
+
const delivered = new Set(deliveries.map(({ loader }) => loader));
|
|
610
617
|
const isReplaced = ({ locale, namespace }) => replaced.some((loader) => loader.locale === locale && loader.namespace === namespace);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
.
|
|
615
|
-
.filter((
|
|
618
|
+
// In the order they were delivered, so across loads the data delivered
|
|
619
|
+
// last wins, as it does where nothing is rebuilt.
|
|
620
|
+
const rebuilt = [
|
|
621
|
+
...Array.from(this.#deliveries.values()).filter(({ loader }) => isReplaced(loader) && !delivered.has(loader)),
|
|
622
|
+
...deliveries.filter(({ loader }) => isReplaced(loader)),
|
|
623
|
+
];
|
|
616
624
|
const raw = replaced.reduce((acc, { locale, namespace }) => ({
|
|
617
625
|
...acc,
|
|
618
626
|
[locale]: Object.fromEntries(Object.entries(read(acc, locale) ?? {}).filter(([key]) => !isNamespaceKey(key, namespace))),
|
|
@@ -642,6 +650,23 @@ class I18nCore {
|
|
|
642
650
|
#addSanitized(sanitized) {
|
|
643
651
|
this.#mergeTranslations(sanitized);
|
|
644
652
|
this.#keepExternal(sanitized);
|
|
653
|
+
const { preprocess } = this.#config ?? {};
|
|
654
|
+
// Seeded over what a loader delivered before, so a rebuild of the
|
|
655
|
+
// namespace, which lays the seeds under every delivery, keeps it on top
|
|
656
|
+
// until that loader delivers again.
|
|
657
|
+
this.#deliveries.forEach((delivery, loader) => {
|
|
658
|
+
const own = Object.entries(read(sanitized, loader.locale) ?? {}).filter(([key]) => isNamespaceKey(key, loader.namespace));
|
|
659
|
+
if (!own.length)
|
|
660
|
+
return;
|
|
661
|
+
const nested = read(read(sanitized, loader.locale), loader.namespace);
|
|
662
|
+
const masked = nested === undefined ? delivery.data : maskTranslations(delivery.data, nested) ?? {};
|
|
663
|
+
// Dot notation, as `#preprocess` applies it, merges both spellings of a
|
|
664
|
+
// key into one.
|
|
665
|
+
const dotted = typeof preprocess !== 'function' && preprocess !== 'none'
|
|
666
|
+
? maskOutputKeys(masked, new Set(Object.keys(toDotNotation(Object.fromEntries(own), preprocess === 'preserveArrays') ?? {})), loader.namespace, preprocess === 'preserveArrays') ?? {}
|
|
667
|
+
: masked;
|
|
668
|
+
this.#deliveries.set(loader, { ...delivery, data: dotted });
|
|
669
|
+
});
|
|
645
670
|
}
|
|
646
671
|
/**
|
|
647
672
|
* Applies plain hand-off data. It records every namespace the data names,
|
|
@@ -672,7 +697,7 @@ class I18nCore {
|
|
|
672
697
|
* that change later replace it; the rest is kept as data supplied without a
|
|
673
698
|
* loader and, like a seed, records no namespace — the hand-off says which loaders it
|
|
674
699
|
* covers, and anything else loads again rather than going missing. `seeds`
|
|
675
|
-
* is what was seeded
|
|
700
|
+
* is what was seeded into those namespaces, displayed and kept as a seed
|
|
676
701
|
* so that it outlives a delivery. A record naming no loader of this config
|
|
677
702
|
* is dropped, and its loader runs again.
|
|
678
703
|
*/
|
|
@@ -705,6 +730,8 @@ class I18nCore {
|
|
|
705
730
|
}
|
|
706
731
|
/** Records `delivery` as its loader's; what was parked for its params is older. */
|
|
707
732
|
#record(delivery) {
|
|
733
|
+
// Deleted first, so the map keeps the order the deliveries arrived in.
|
|
734
|
+
this.#deliveries.delete(delivery.loader);
|
|
708
735
|
this.#deliveries.set(delivery.loader, delivery);
|
|
709
736
|
this.#loaderRecords.set(delivery.loader, delivery.signature);
|
|
710
737
|
if (this.#parked.get(delivery.loader)?.signature === delivery.signature)
|
package/dist/types.d.ts
CHANGED
|
@@ -628,10 +628,11 @@ export declare namespace Snapshot {
|
|
|
628
628
|
*/
|
|
629
629
|
records?: LoadRecord[];
|
|
630
630
|
/**
|
|
631
|
-
* What was seeded into the namespace of a loader
|
|
632
|
-
* params – repeated where `translations` carries the
|
|
633
|
-
* carried where the payload leaves it out, so that it
|
|
634
|
-
* delivery
|
|
631
|
+
* What was seeded into the namespace of a recorded loader or of one whose
|
|
632
|
+
* routes capture params – repeated where `translations` carries the
|
|
633
|
+
* namespace, and carried where the payload leaves it out, so that it
|
|
634
|
+
* outlives the delivery the loader's next fetch replaces. Read with
|
|
635
|
+
* `records` only.
|
|
635
636
|
*/
|
|
636
637
|
seeds?: Translations.SerializedTranslations;
|
|
637
638
|
/** The active locale. */
|
|
@@ -645,9 +646,11 @@ export declare namespace Translations {
|
|
|
645
646
|
type SerializedTranslations = LocaleIndexed<DotNotation.Input>;
|
|
646
647
|
/**
|
|
647
648
|
* What `t`/`l` yield. The parser's output on the paths that reach the parser,
|
|
648
|
-
* and a plain string on the
|
|
649
|
-
*
|
|
650
|
-
*
|
|
649
|
+
* and a plain string on the misses: `''` for an empty key or no locale, the
|
|
650
|
+
* key itself for a missing translation. Two paths hand back data unchecked:
|
|
651
|
+
* a configured `fallbackValue`, and the stored value while the config
|
|
652
|
+
* carries no parser. `O` is `string` for every parser that returns one,
|
|
653
|
+
* which collapses the union everywhere it is not needed.
|
|
651
654
|
*/
|
|
652
655
|
type Translated<O> = O | string;
|
|
653
656
|
type TranslationFunction<P extends Parser.Params = Parser.Params, O = string, S = never> = <K extends Schema.Key<S>>(key: K, ...restParams: Schema.Params<S, K, P>) => Translated<O>;
|
package/dist/utils.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export declare const toDotNotation: DotNotation.T;
|
|
|
54
54
|
export declare const unique: <V>(values: readonly V[]) => V[];
|
|
55
55
|
export declare const resolveLoaders: (input?: readonly Loader.LoaderModule[], sanitizeLocales?: Config.SanitizeLocales) => Loader.Resolved[];
|
|
56
56
|
export declare const mergeTranslations: (target: any, source: any, path: string, onConflict?: (path: string) => void) => any;
|
|
57
|
+
export declare const maskTranslations: (target: any, source: any) => any;
|
|
58
|
+
export declare const maskOutputKeys: (target: any, keys: ReadonlySet<string>, prefix: string, preserveArrays?: boolean) => any;
|
|
57
59
|
export declare const omitProtoKeys: (value: any) => any;
|
|
58
60
|
export declare const serialize: (input: Array<Loader.Resolved & {
|
|
59
61
|
data: any;
|
package/dist/utils.js
CHANGED
|
@@ -492,6 +492,42 @@ export const mergeTranslations = (target, source, path, onConflict) => {
|
|
|
492
492
|
[key]: hasOwn(acc, key) ? mergeTranslations(read(acc, key), read(source, key), `${path}.${key}`, onConflict) : read(source, key),
|
|
493
493
|
}), target);
|
|
494
494
|
};
|
|
495
|
+
// What of `target` still shows once `source` is merged over it: the branches
|
|
496
|
+
// both hold as objects keep what `source` leaves out, and anything else it
|
|
497
|
+
// sets is gone. `undefined` when `source` replaces the whole of `target`.
|
|
498
|
+
export const maskTranslations = (target, source) => {
|
|
499
|
+
if (!isMergeable(target) || !isMergeable(source))
|
|
500
|
+
return undefined;
|
|
501
|
+
return Object.keys(target).reduce((acc, key) => {
|
|
502
|
+
if (!hasOwn(source, key))
|
|
503
|
+
return { ...acc, [key]: read(target, key) };
|
|
504
|
+
const masked = maskTranslations(read(target, key), read(source, key));
|
|
505
|
+
return masked === undefined ? acc : { ...acc, [key]: masked };
|
|
506
|
+
}, {});
|
|
507
|
+
};
|
|
508
|
+
// What of `target`, dot-notated under `prefix`, still shows once `keys` are
|
|
509
|
+
// dot-notated beside it: a leaf whose key is among them is gone. A key spelled
|
|
510
|
+
// with dots and a nested branch meet only there, after `preprocess`. Unless
|
|
511
|
+
// `preserveArrays`, an array is walked as `toDotNotation` walks it, and one
|
|
512
|
+
// that lost an item keeps the others under their indexes. What lost nothing is
|
|
513
|
+
// returned as it is, and an array that lost no item and holds nothing but its
|
|
514
|
+
// items stays one.
|
|
515
|
+
export const maskOutputKeys = (target, keys, prefix, preserveArrays = false) => {
|
|
516
|
+
const walked = Array.isArray(target) ? !preserveArrays : isMergeable(target);
|
|
517
|
+
if (!walked)
|
|
518
|
+
return keys.has(prefix) ? undefined : target;
|
|
519
|
+
const entries = Object.keys(target).flatMap((key) => {
|
|
520
|
+
const masked = maskOutputKeys(read(target, key), keys, `${prefix}.${key}`, preserveArrays);
|
|
521
|
+
return masked === undefined ? [] : [[key, masked]];
|
|
522
|
+
});
|
|
523
|
+
if (entries.length === Object.keys(target).length) {
|
|
524
|
+
if (entries.every(([key, value]) => Object.is(value, read(target, key))))
|
|
525
|
+
return target;
|
|
526
|
+
if (Array.isArray(target) && entries.every(([key], index) => key === `${index}`))
|
|
527
|
+
return entries.map(([, value]) => value);
|
|
528
|
+
}
|
|
529
|
+
return entries.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
|
530
|
+
};
|
|
495
531
|
const isPlainObject = (value) => {
|
|
496
532
|
if (!value || typeof value !== 'object')
|
|
497
533
|
return false;
|
package/package.json
CHANGED