@kosdev-code/kos-ui-sdk 0.1.0-next.861 → 0.1.0-next.864
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/index-CvCXisvT.js +13535 -0
- package/index-CvCXisvT.js.map +1 -0
- package/index-D8aw-T5w.cjs +13532 -0
- package/index-D8aw-T5w.cjs.map +1 -0
- package/index.cjs +1074 -14356
- package/index.cjs.map +1 -1
- package/index.js +522 -13811
- package/index.js.map +1 -1
- package/models/models/translation/descriptor-merge.d.ts +38 -0
- package/models/models/translation/descriptor-merge.d.ts.map +1 -0
- package/models/models/translation/index.d.ts +2 -0
- package/models/models/translation/index.d.ts.map +1 -1
- package/models/models/translation/test-harness.d.ts +36 -0
- package/models/models/translation/test-harness.d.ts.map +1 -0
- package/models/models/translation/translation-container-model.d.ts +2 -0
- package/models/models/translation/translation-container-model.d.ts.map +1 -1
- package/models/models/translation/translation-model.d.ts.map +1 -1
- package/models/models/translation/types/index.d.ts +26 -0
- package/package.json +13 -3
- package/testing/index.d.ts +33 -0
- package/testing/index.d.ts.map +1 -0
- package/testing.cjs +26 -0
- package/testing.cjs.map +1 -0
- package/testing.d.cts +2 -0
- package/testing.d.ts +2 -0
- package/testing.js +26 -0
- package/testing.js.map +1 -0
- package/ui/contexts/translation-container/kos-translations-provider.d.ts.map +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Localization } from './services';
|
|
2
|
+
/** A locale code paired with its name, rendered in that locale's own language. */
|
|
3
|
+
export interface LocaleOption {
|
|
4
|
+
locale: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Fold contexts into one descriptor, EARLIER ENTRIES WINNING.
|
|
9
|
+
*
|
|
10
|
+
* Precedence is per NAMESPACE, not per key or per locale: a namespace declared
|
|
11
|
+
* by two contexts is taken whole from the earlier one, and the later context's
|
|
12
|
+
* locales for that namespace are dropped even where the winner has none. Adding
|
|
13
|
+
* a context therefore only ever ADDS namespaces the earlier ones did not
|
|
14
|
+
* declare — it can never extend or patch one they did.
|
|
15
|
+
*
|
|
16
|
+
* Each namespace's `basePath` is set to its own context's `path`, so namespaces
|
|
17
|
+
* from different contexts keep resolving against the URL their context is
|
|
18
|
+
* served from.
|
|
19
|
+
*
|
|
20
|
+
* The merged descriptor carries no top-level `path`: with namespaces from
|
|
21
|
+
* several contexts there is no single one, and `basePath` already carries it
|
|
22
|
+
* per namespace, which is what `defaultResolver` reads.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeLocalizationDescriptors(descriptors: Localization[]): Localization;
|
|
25
|
+
/**
|
|
26
|
+
* Every locale any namespace offers, as picker options.
|
|
27
|
+
*
|
|
28
|
+
* A UNION across namespaces, so one namespace translated into a locale is
|
|
29
|
+
* enough to offer that locale for the whole app; namespaces without it fall
|
|
30
|
+
* back at resolve time. Excluded namespaces contribute nothing — that is how a
|
|
31
|
+
* content-bearing namespace (a brandset) is kept from advertising locales the
|
|
32
|
+
* UI itself cannot render.
|
|
33
|
+
*
|
|
34
|
+
* Labels come from `Intl.DisplayNames`, not from any KAB, and are rendered in
|
|
35
|
+
* the locale's own language ("italiano", not "Italian").
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectLocaleOptions(descriptors: Localization[], excludeNamespaces?: string[]): LocaleOption[];
|
|
38
|
+
//# sourceMappingURL=descriptor-merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor-merge.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/descriptor-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,YAAY,EAAE,GAC1B,YAAY,CAad;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,YAAY,EAAE,EAC3B,iBAAiB,GAAE,MAAM,EAAO,GAC/B,YAAY,EAAE,CAsBhB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { getKosLocalizationDescriptor, getLocalizationDescriptor, getTranslations, } from './services';
|
|
2
2
|
export type { Localization } from './services';
|
|
3
|
+
export { collectLocaleOptions, mergeLocalizationDescriptors, } from './descriptor-merge';
|
|
4
|
+
export type { LocaleOption } from './descriptor-merge';
|
|
3
5
|
export type { TranslationContainerModel } from './translation-container-model';
|
|
4
6
|
export { TranslationContainer } from './translation-container-registration';
|
|
5
7
|
export * from './translation-context';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,cAAc,uBAAuB,CAAC;AACtC,YAAY,EACV,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,mBAAmB,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EACL,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,cAAc,uBAAuB,CAAC;AACtC,YAAY,EACV,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,mBAAmB,SAAS,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { until } from '../../../testing';
|
|
2
|
+
import { Localization, TranslationResponse } from './services';
|
|
3
|
+
import { KosTranslations } from './translation-context';
|
|
4
|
+
/** Namespace → locale → the bundle that locale serves. */
|
|
5
|
+
export type Bundles = Record<string, Record<string, TranslationResponse>>;
|
|
6
|
+
/**
|
|
7
|
+
* A descriptor covering `bundles`, with every locale falling back to `en`
|
|
8
|
+
* unless the caller says otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export declare function descriptorFor(bundles: Bundles, fallbacks?: Record<string, string[]>): Localization;
|
|
11
|
+
/**
|
|
12
|
+
* ONE CONTAINER PER TEST FILE. TranslationContainer is a singleton keyed by
|
|
13
|
+
* model type, so a second `harness()` call in the same file returns the FIRST
|
|
14
|
+
* container with its original descriptor and locale — the new options are
|
|
15
|
+
* discarded in silence, and assertions written against them pass for the wrong
|
|
16
|
+
* reason. A case needing a different locale or descriptor belongs in its own
|
|
17
|
+
* file, which vitest runs with its own module state.
|
|
18
|
+
*/
|
|
19
|
+
export interface Harness {
|
|
20
|
+
container: Awaited<ReturnType<typeof KosTranslations.init>>["translations"];
|
|
21
|
+
/** Every (namespace, locale) the container has asked for, in order. */
|
|
22
|
+
loads: string[];
|
|
23
|
+
/** Resolve a namespace and wait for its first load to finish. */
|
|
24
|
+
namespace: (name: string) => Promise<ReturnType<Harness["container"]["resolveNamespace"]>>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A ready container serving `bundles`, plus a log of what it loaded — the log
|
|
28
|
+
* is how a test tells a genuine reload from a cached read.
|
|
29
|
+
*/
|
|
30
|
+
export declare function harness(bundles: Bundles, options?: {
|
|
31
|
+
lang?: string;
|
|
32
|
+
fallbacks?: Record<string, string[]>;
|
|
33
|
+
defaultNamespace?: string;
|
|
34
|
+
}): Promise<Harness>;
|
|
35
|
+
export { until };
|
|
36
|
+
//# sourceMappingURL=test-harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-harness.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/test-harness.ts"],"names":[],"mappings":"AAaA,OAAO,EAAe,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIxD,0DAA0D;AAC1D,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAM,GACvC,YAAY,CAoBd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5E,uEAAuE;IACvE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iEAAiE;IACjE,SAAS,EAAE,CACT,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;CACpE;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACtB,GACL,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
|
@@ -101,6 +101,8 @@ export declare class TranslationContainerModelImpl implements IKosIdentifiable,
|
|
|
101
101
|
private descriptorUrl?;
|
|
102
102
|
/** Array tracking registered namespace identifiers */
|
|
103
103
|
private namespaces;
|
|
104
|
+
/** Container-wide bundle loader, handed to every namespace it resolves */
|
|
105
|
+
private bundleResolver?;
|
|
104
106
|
/** Container for managing Translation model instances */
|
|
105
107
|
private _models;
|
|
106
108
|
constructor(modelId: string, options: TranslationContainerOptions, context: KosCreationContext);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-container-model.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/translation-container-model.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAEf,kBAAkB,EAClB,WAAW,EACZ,MAAM,eAAe,CAAC;AASvB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,2BAA2B,
|
|
1
|
+
{"version":3,"file":"translation-container-model.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/translation-container-model.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAEf,kBAAkB,EAClB,WAAW,EACZ,MAAM,eAAe,CAAC;AASvB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,2BAA2B,EAAsB,MAAM,SAAS,CAAC;AAE/E,eAAO,MAAM,UAAU,gCAAgC,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,MAAM,MAAM,yBAAyB,GACnC,oBAAoB,CAAC,6BAA6B,CAAC,CAAC;AAmBtD;;;GAGG;AACH,qBACa,6BACX,YACE,gBAAgB,EAChB,eAAe,CAAC,gBAAgB,CAAC,EACjC,aAAa,EACb,WAAW;IAEb,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,mFAAmF;IACnF,UAAU,EAAE,YAAY,CAAC;IACzB,iFAAiF;IACjF,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAIJ;IACZ,0DAA0D;IAC1D,OAAO,CAAC,MAAM,CAAmB;IACjC,2DAA2D;IAC3D,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,sDAAsD;IACtD,OAAO,CAAC,UAAU,CAAgB;IAClC,0EAA0E;IAC1E,OAAO,CAAC,cAAc,CAAC,CAAuC;IAC9D,yDAAyD;IAC/C,OAAO,CAAC,OAAO,CAAuC;gBAE9D,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,2BAA2B,EACpC,OAAO,EAAE,kBAAkB;IAkB7B,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,EAK/B;IACD,IAAI,gBAAgB,iCAInB;IACD,QAAQ,CAAC,EAAE,EAAE,MAAM;IAInB,IAAI,MAAM,yCAET;IACD,IAAI,IAAI,uBAEP;IAEK,MAAM;IAMZ,gBAAgB,CAAC,SAAS,EAAE,MAAM;IA8ClC,QAAQ,CAAC,KAAK,EAAE,gBAAgB;IAIhC,WAAW,CAAC,EAAE,EAAE,MAAM;IAKtB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-model.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/translation-model.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EAEV,kBAAkB,EACnB,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,eAAO,MAAM,UAAU,sBAAsB,CAAC;AAC9C,UAAU,6BAA6B,CAAC,CAAC,GAAG,MAAM;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,CAAC;CAClB;AAED,UAAU,8BAA8B,CAAC,CAAC,GAAG,MAAM,CACjD,SAAQ,6BAA6B,CAAC,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;CACpC;AAED,KAAK,6BAA6B,CAAC,CAAC,GAAG,MAAM,IAC3C,6BAA6B,CAAC,CAAC,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AACjE,MAAM,MAAM,yBAAyB,CAAC,CAAC,GAAG,MAAM,IAC5C,8BAA8B,CAAC,CAAC,CAAC,GACjC,6BAA6B,CAAC,CAAC,CAAC,CAAC;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"translation-model.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/models/models/translation/translation-model.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EAEV,kBAAkB,EACnB,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,eAAO,MAAM,UAAU,sBAAsB,CAAC;AAC9C,UAAU,6BAA6B,CAAC,CAAC,GAAG,MAAM;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,CAAC;CAClB;AAED,UAAU,8BAA8B,CAAC,CAAC,GAAG,MAAM,CACjD,SAAQ,6BAA6B,CAAC,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;CACpC;AAED,KAAK,6BAA6B,CAAC,CAAC,GAAG,MAAM,IAC3C,6BAA6B,CAAC,CAAC,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AACjE,MAAM,MAAM,yBAAyB,CAAC,CAAC,GAAG,MAAM,IAC5C,8BAA8B,CAAC,CAAC,CAAC,GACjC,6BAA6B,CAAC,CAAC,CAAC,CAAC;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;AAqE1E,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAoBnE,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,MAAM,MAAM,EACZ,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,UAAU,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QA0C9B,CAAC;AAUF,qBAEa,oBAAqB,YAAW,aAAa,EAAE,gBAAgB;IAC1E,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,UAAU,KACjB,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,0DAA0D;IAC1D,OAAO,CAAC,IAAI,CAAsB;IAClC,mDAAmD;IACnD,OAAO,CAAC,MAAM,CAAmB;IACjC,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAgD;IAChE,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAC,CAAoB;IACrC,sEAAsE;IACtE,OAAO,CAAC,OAAO,CAA4B;IAC3C,iDAAiD;IACjD,OAAO,CAAC,OAAO,CAAC,CAAS;gBAEvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,kBAAkB;IAqB7B;;;;;OAKG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM;IAepC;;;OAGG;YACW,gBAAgB;IAoC9B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM;IAYnB;;;;;OAKG;IACH,UAAU,CAAC,CAAC,GAAG,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,MAAM;IAmCjD;;OAEG;IACH,MAAM,IAAI,IAAI;IAOd;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,yBAAyB;CAGhC"}
|
|
@@ -311,6 +311,32 @@ export interface TranslationContainerOptions {
|
|
|
311
311
|
namespace: string,
|
|
312
312
|
locale: string
|
|
313
313
|
) => string;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Optional custom bundle loader, applied to every namespace this container
|
|
317
|
+
* resolves. Replaces path resolution entirely: when supplied, `resolver` is
|
|
318
|
+
* not consulted and no file is fetched — the container asks this function for
|
|
319
|
+
* the translation data instead.
|
|
320
|
+
*
|
|
321
|
+
* Each namespace's Translation model calls it once per locale in its fallback
|
|
322
|
+
* chain, and the results are merged in that chain's order exactly as loaded
|
|
323
|
+
* files are. A per-namespace `bundleResolver` passed directly to
|
|
324
|
+
* `Translation` still wins over this one.
|
|
325
|
+
*
|
|
326
|
+
* This is the seam for loading translations from somewhere other than static
|
|
327
|
+
* files — an API, device storage, an in-memory catalog — and the seam tests
|
|
328
|
+
* use to drive a container without a network.
|
|
329
|
+
*
|
|
330
|
+
* @param namespace - Target translation namespace
|
|
331
|
+
* @param locale - Target locale code
|
|
332
|
+
* @param context - KOS context for accessing shared data
|
|
333
|
+
* @returns Promise resolving to translation data
|
|
334
|
+
*/
|
|
335
|
+
bundleResolver?: (
|
|
336
|
+
namespace: string,
|
|
337
|
+
locale: string,
|
|
338
|
+
context?: KosContext
|
|
339
|
+
) => Promise<TranslationResponse>;
|
|
314
340
|
}
|
|
315
341
|
|
|
316
342
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kosdev-code/kos-ui-sdk",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.864",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.cjs",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
"default": "./index.cjs"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
+
"./testing": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./testing.d.ts",
|
|
36
|
+
"default": "./testing.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./testing.d.cts",
|
|
40
|
+
"default": "./testing.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
33
43
|
"./package.json": "./package.json"
|
|
34
44
|
},
|
|
35
45
|
"peerDependencies": {
|
|
@@ -40,7 +50,7 @@
|
|
|
40
50
|
"react-router-dom": "^6.11.2"
|
|
41
51
|
},
|
|
42
52
|
"dependencies": {
|
|
43
|
-
"@kosdev-code/kos-api-levels": "0.1.0-next.
|
|
53
|
+
"@kosdev-code/kos-api-levels": "0.1.0-next.864",
|
|
44
54
|
"mobx": "^6.9.0",
|
|
45
55
|
"mobx-react-lite": "^3.4.3",
|
|
46
56
|
"date-fns": "^2.30.0",
|
|
@@ -49,7 +59,7 @@
|
|
|
49
59
|
},
|
|
50
60
|
"kos": {
|
|
51
61
|
"build": {
|
|
52
|
-
"gitHash": "
|
|
62
|
+
"gitHash": "8a9f3bb7a9752d3d7beeaa86f771e474184815d1"
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IKosCoreManager, IKosRegistry } from '../core';
|
|
2
|
+
export interface BootKosCoreOptions {
|
|
3
|
+
/** Connection alias, when the model under test cares about one. */
|
|
4
|
+
alias?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Boot KosCore on the standalone mock transport and resolve once it is ready.
|
|
8
|
+
*
|
|
9
|
+
* Idempotent per module instance: the first call boots, later calls return the
|
|
10
|
+
* same core. KosCore and the model manager are process-global, so a test file
|
|
11
|
+
* gets one core and one set of singletons — which is the isolation boundary,
|
|
12
|
+
* and the reason a differently-configured singleton needs its own file.
|
|
13
|
+
*
|
|
14
|
+
* Register any routes the models need BEFORE calling this, so nothing races the
|
|
15
|
+
* boot requests.
|
|
16
|
+
*
|
|
17
|
+
* @param registration Models under test, e.g. `{ models: {...Board.registration}, preloadModels: [] }`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bootKosCore(registration: IKosRegistry, options?: BootKosCoreOptions): Promise<IKosCoreManager>;
|
|
20
|
+
/**
|
|
21
|
+
* Wait until `predicate` holds, then resolve — or reject naming what never
|
|
22
|
+
* happened.
|
|
23
|
+
*
|
|
24
|
+
* Model reactions are asynchronous, so a test that changes an input and asserts
|
|
25
|
+
* immediately reads the old value. Polling for the OUTCOME beats sleeping for an
|
|
26
|
+
* arbitrary interval: it is faster when the reaction is quick, and it fails with
|
|
27
|
+
* a usable message instead of a bare assertion diff when the reaction never
|
|
28
|
+
* fires at all.
|
|
29
|
+
*
|
|
30
|
+
* @param label What is being waited for, quoted back in the timeout message.
|
|
31
|
+
*/
|
|
32
|
+
export declare function until(predicate: () => boolean, label: string, timeoutMs?: number): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/kos-ui-sdk/src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK7D,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,YAAY,EAC1B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,KAAK,CACzB,SAAS,EAAE,MAAM,OAAO,EACxB,KAAK,EAAE,MAAM,EACb,SAAS,SAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
package/testing.cjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
require("mobx");
|
|
4
|
+
const service = require("./index-D8aw-T5w.cjs");
|
|
5
|
+
require("loglevel");
|
|
6
|
+
let booted;
|
|
7
|
+
function bootKosCore(registration, options = {}) {
|
|
8
|
+
if (!booted) {
|
|
9
|
+
service.KosMock.configure({ standalone: true });
|
|
10
|
+
const core = service.KosCore.create(registration, false, options.alias);
|
|
11
|
+
booted = core.whenReady().then(() => core);
|
|
12
|
+
}
|
|
13
|
+
return booted;
|
|
14
|
+
}
|
|
15
|
+
async function until(predicate, label, timeoutMs = 2e3) {
|
|
16
|
+
const deadline = Date.now() + timeoutMs;
|
|
17
|
+
while (!predicate()) {
|
|
18
|
+
if (Date.now() > deadline) {
|
|
19
|
+
throw new Error(`timed out waiting for ${label}`);
|
|
20
|
+
}
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.bootKosCore = bootKosCore;
|
|
25
|
+
exports.until = until;
|
|
26
|
+
//# sourceMappingURL=testing.cjs.map
|
package/testing.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.cjs","sources":["../../../packages/kos-ui-sdk/src/testing/index.ts"],"sourcesContent":["/**\n * Test support for KOS models — boot the framework in a unit test, with no\n * device, no WebSocket server and no browser.\n *\n * KOS models cannot be built in isolation: they need a live KosCore, and\n * KosCore opens a WebSocket the moment it is created. `bootKosCore` boots it on\n * KosMock's standalone loopback transport instead, which authorizes itself so\n * `whenReady()` resolves with no device and no `KOS_ALLOW_ANONYMOUS` build flag.\n *\n * Backend traffic is mocked the same way an app would mock it — `KosMock.get`,\n * `KosMock.post`, fixtures, config injection — so a test exercises the real\n * request path rather than a stubbed service function.\n *\n * ```ts\n * import { bootKosCore, until } from \"@kosdev-code/kos-ui-sdk/testing\";\n * import { KosMock } from \"@kosdev-code/kos-ui-sdk\";\n *\n * beforeAll(async () => {\n * KosMock.get(\"/api/board/:id\", { data: { id: \"b1\", state: \"READY\" } });\n * await bootKosCore({ models: { ...Board.registration }, preloadModels: [] });\n * });\n *\n * it(\"loads the board\", async () => {\n * const board = Board.factory(\"b1\")({});\n * await whenReady(board);\n * expect(board.state).toBe(\"READY\");\n * });\n * ```\n *\n * ## Two things that bite\n *\n * **Singleton models take one configuration per test file.** A second\n * `factory(options)` call returns the FIRST instance and discards the new\n * options in silence, so assertions written against them pass for the wrong\n * reason. A case needing different options belongs in its own file, which the\n * test runner gives its own module state.\n *\n * **The standalone socket authorizes by publishing Studio's\n * `/studioServer/auth/LOGGED_IN` on every open.** A device never sends that, so\n * a test shaped around disconnect/reconnect here is exercising Studio's\n * sequence, not a device's. It makes no difference to tests that never\n * reconnect.\n */\nimport type { IKosCoreManager, IKosRegistry } from \"../core\";\nimport { KosCore, KosMock } from \"../core\";\n\nlet booted: Promise<IKosCoreManager> | undefined;\n\nexport interface BootKosCoreOptions {\n /** Connection alias, when the model under test cares about one. */\n alias?: string;\n}\n\n/**\n * Boot KosCore on the standalone mock transport and resolve once it is ready.\n *\n * Idempotent per module instance: the first call boots, later calls return the\n * same core. KosCore and the model manager are process-global, so a test file\n * gets one core and one set of singletons — which is the isolation boundary,\n * and the reason a differently-configured singleton needs its own file.\n *\n * Register any routes the models need BEFORE calling this, so nothing races the\n * boot requests.\n *\n * @param registration Models under test, e.g. `{ models: {...Board.registration}, preloadModels: [] }`.\n */\nexport function bootKosCore(\n registration: IKosRegistry,\n options: BootKosCoreOptions = {}\n): Promise<IKosCoreManager> {\n if (!booted) {\n // Must precede KosCore.create: TransportFactory picks the transport once.\n KosMock.configure({ standalone: true });\n const core = KosCore.create(registration, false, options.alias);\n booted = core.whenReady().then(() => core);\n }\n return booted;\n}\n\n/**\n * Wait until `predicate` holds, then resolve — or reject naming what never\n * happened.\n *\n * Model reactions are asynchronous, so a test that changes an input and asserts\n * immediately reads the old value. Polling for the OUTCOME beats sleeping for an\n * arbitrary interval: it is faster when the reaction is quick, and it fails with\n * a usable message instead of a bare assertion diff when the reaction never\n * fires at all.\n *\n * @param label What is being waited for, quoted back in the timeout message.\n */\nexport async function until(\n predicate: () => boolean,\n label: string,\n timeoutMs = 2000\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (!predicate()) {\n if (Date.now() > deadline) {\n throw new Error(`timed out waiting for ${label}`);\n }\n await new Promise((resolve) => setTimeout(resolve, 5));\n }\n}\n"],"names":["KosMock","KosCore"],"mappings":";;;;;AA8CA,IAAI;AAoBG,SAAS,YACd,cACA,UAA8B,IACJ;AAC1B,MAAI,CAAC,QAAQ;AAEXA,YAAAA,QAAQ,UAAU,EAAE,YAAY,KAAA,CAAM;AACtC,UAAM,OAAOC,QAAAA,QAAQ,OAAO,cAAc,OAAO,QAAQ,KAAK;AAC9D,aAAS,KAAK,UAAA,EAAY,KAAK,MAAM,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;AAcA,eAAsB,MACpB,WACA,OACA,YAAY,KACG;AACf,QAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,SAAO,CAAC,aAAa;AACnB,QAAI,KAAK,IAAA,IAAQ,UAAU;AACzB,YAAM,IAAI,MAAM,yBAAyB,KAAK,EAAE;AAAA,IAClD;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAAA,EACvD;AACF;;;"}
|
package/testing.d.cts
ADDED
package/testing.d.ts
ADDED
package/testing.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "mobx";
|
|
2
|
+
import { b0 as KosMock, l as KosCore } from "./index-CvCXisvT.js";
|
|
3
|
+
import "loglevel";
|
|
4
|
+
let booted;
|
|
5
|
+
function bootKosCore(registration, options = {}) {
|
|
6
|
+
if (!booted) {
|
|
7
|
+
KosMock.configure({ standalone: true });
|
|
8
|
+
const core = KosCore.create(registration, false, options.alias);
|
|
9
|
+
booted = core.whenReady().then(() => core);
|
|
10
|
+
}
|
|
11
|
+
return booted;
|
|
12
|
+
}
|
|
13
|
+
async function until(predicate, label, timeoutMs = 2e3) {
|
|
14
|
+
const deadline = Date.now() + timeoutMs;
|
|
15
|
+
while (!predicate()) {
|
|
16
|
+
if (Date.now() > deadline) {
|
|
17
|
+
throw new Error(`timed out waiting for ${label}`);
|
|
18
|
+
}
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
bootKosCore,
|
|
24
|
+
until
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=testing.js.map
|
package/testing.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sources":["../../../packages/kos-ui-sdk/src/testing/index.ts"],"sourcesContent":["/**\n * Test support for KOS models — boot the framework in a unit test, with no\n * device, no WebSocket server and no browser.\n *\n * KOS models cannot be built in isolation: they need a live KosCore, and\n * KosCore opens a WebSocket the moment it is created. `bootKosCore` boots it on\n * KosMock's standalone loopback transport instead, which authorizes itself so\n * `whenReady()` resolves with no device and no `KOS_ALLOW_ANONYMOUS` build flag.\n *\n * Backend traffic is mocked the same way an app would mock it — `KosMock.get`,\n * `KosMock.post`, fixtures, config injection — so a test exercises the real\n * request path rather than a stubbed service function.\n *\n * ```ts\n * import { bootKosCore, until } from \"@kosdev-code/kos-ui-sdk/testing\";\n * import { KosMock } from \"@kosdev-code/kos-ui-sdk\";\n *\n * beforeAll(async () => {\n * KosMock.get(\"/api/board/:id\", { data: { id: \"b1\", state: \"READY\" } });\n * await bootKosCore({ models: { ...Board.registration }, preloadModels: [] });\n * });\n *\n * it(\"loads the board\", async () => {\n * const board = Board.factory(\"b1\")({});\n * await whenReady(board);\n * expect(board.state).toBe(\"READY\");\n * });\n * ```\n *\n * ## Two things that bite\n *\n * **Singleton models take one configuration per test file.** A second\n * `factory(options)` call returns the FIRST instance and discards the new\n * options in silence, so assertions written against them pass for the wrong\n * reason. A case needing different options belongs in its own file, which the\n * test runner gives its own module state.\n *\n * **The standalone socket authorizes by publishing Studio's\n * `/studioServer/auth/LOGGED_IN` on every open.** A device never sends that, so\n * a test shaped around disconnect/reconnect here is exercising Studio's\n * sequence, not a device's. It makes no difference to tests that never\n * reconnect.\n */\nimport type { IKosCoreManager, IKosRegistry } from \"../core\";\nimport { KosCore, KosMock } from \"../core\";\n\nlet booted: Promise<IKosCoreManager> | undefined;\n\nexport interface BootKosCoreOptions {\n /** Connection alias, when the model under test cares about one. */\n alias?: string;\n}\n\n/**\n * Boot KosCore on the standalone mock transport and resolve once it is ready.\n *\n * Idempotent per module instance: the first call boots, later calls return the\n * same core. KosCore and the model manager are process-global, so a test file\n * gets one core and one set of singletons — which is the isolation boundary,\n * and the reason a differently-configured singleton needs its own file.\n *\n * Register any routes the models need BEFORE calling this, so nothing races the\n * boot requests.\n *\n * @param registration Models under test, e.g. `{ models: {...Board.registration}, preloadModels: [] }`.\n */\nexport function bootKosCore(\n registration: IKosRegistry,\n options: BootKosCoreOptions = {}\n): Promise<IKosCoreManager> {\n if (!booted) {\n // Must precede KosCore.create: TransportFactory picks the transport once.\n KosMock.configure({ standalone: true });\n const core = KosCore.create(registration, false, options.alias);\n booted = core.whenReady().then(() => core);\n }\n return booted;\n}\n\n/**\n * Wait until `predicate` holds, then resolve — or reject naming what never\n * happened.\n *\n * Model reactions are asynchronous, so a test that changes an input and asserts\n * immediately reads the old value. Polling for the OUTCOME beats sleeping for an\n * arbitrary interval: it is faster when the reaction is quick, and it fails with\n * a usable message instead of a bare assertion diff when the reaction never\n * fires at all.\n *\n * @param label What is being waited for, quoted back in the timeout message.\n */\nexport async function until(\n predicate: () => boolean,\n label: string,\n timeoutMs = 2000\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (!predicate()) {\n if (Date.now() > deadline) {\n throw new Error(`timed out waiting for ${label}`);\n }\n await new Promise((resolve) => setTimeout(resolve, 5));\n }\n}\n"],"names":[],"mappings":";;;AA8CA,IAAI;AAoBG,SAAS,YACd,cACA,UAA8B,IACJ;AAC1B,MAAI,CAAC,QAAQ;AAEX,YAAQ,UAAU,EAAE,YAAY,KAAA,CAAM;AACtC,UAAM,OAAO,QAAQ,OAAO,cAAc,OAAO,QAAQ,KAAK;AAC9D,aAAS,KAAK,UAAA,EAAY,KAAK,MAAM,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;AAcA,eAAsB,MACpB,WACA,OACA,YAAY,KACG;AACf,QAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,SAAO,CAAC,aAAa;AACnB,QAAI,KAAK,IAAA,IAAQ,UAAU;AACzB,YAAM,IAAI,MAAM,yBAAyB,KAAK,EAAE;AAAA,IAClD;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAAA,EACvD;AACF;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kos-translations-provider.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/ui/contexts/translation-container/kos-translations-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,YAAY,EACZ,yBAAyB,
|
|
1
|
+
{"version":3,"file":"kos-translations-provider.d.ts","sourceRoot":"","sources":["../../../../../../packages/kos-ui-sdk/src/ui/contexts/translation-container/kos-translations-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,YAAY,EACZ,yBAAyB,EAI1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,iBAAiB,EAMlB,MAAM,OAAO,CAAC;AAEf,UAAU,wBAAwB;IAChC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AACD,eAAO,MAAM,2BAA2B,+DAE5B,CAAC;AAEb,UAAU,iCAAiC;IACzC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,CACT,IAAI,EAAE,yBAAyB,EAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,KACX,MAAM,CAAC;CACb;AAUD,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,iBAAiB,CAC1D,iBAAiB,CAAC,iCAAiC,CAAC,CAyFrD,CAAC;AACF,eAAO,MAAM,wBAAwB,gCAQpC,CAAC"}
|