@orthacms/i18n-server 0.0.0-reserve.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/lib/content/controllers/get-entry-locales.controller.d.ts +15 -0
- package/dist/lib/content/controllers/get-entry-locales.controller.d.ts.map +1 -0
- package/dist/lib/content/controllers/get-entry-locales.controller.js +45 -0
- package/dist/lib/content/controllers/locale-summary.controller.d.ts +30 -0
- package/dist/lib/content/controllers/locale-summary.controller.d.ts.map +1 -0
- package/dist/lib/content/controllers/locale-summary.controller.js +61 -0
- package/dist/lib/content/controllers/resolve-type.d.ts +9 -0
- package/dist/lib/content/controllers/resolve-type.d.ts.map +1 -0
- package/dist/lib/content/controllers/resolve-type.js +20 -0
- package/dist/lib/content/dto/locale-summary.dto.d.ts +10 -0
- package/dist/lib/content/dto/locale-summary.dto.d.ts.map +1 -0
- package/dist/lib/content/dto/locale-summary.dto.js +30 -0
- package/dist/lib/content/locale-group-lock.d.ts +34 -0
- package/dist/lib/content/locale-group-lock.d.ts.map +1 -0
- package/dist/lib/content/locale-group-lock.js +38 -0
- package/dist/lib/content/services/entry-locale-extension.service.d.ts +188 -0
- package/dist/lib/content/services/entry-locale-extension.service.d.ts.map +1 -0
- package/dist/lib/content/services/entry-locale-extension.service.js +662 -0
- package/dist/lib/content/services/locale-group.service.d.ts +87 -0
- package/dist/lib/content/services/locale-group.service.d.ts.map +1 -0
- package/dist/lib/content/services/locale-group.service.js +135 -0
- package/dist/lib/copilot/i18n-tool.provider.d.ts +53 -0
- package/dist/lib/copilot/i18n-tool.provider.d.ts.map +1 -0
- package/dist/lib/copilot/i18n-tool.provider.js +157 -0
- package/dist/lib/copilot/translation-proposal.applier.d.ts +87 -0
- package/dist/lib/copilot/translation-proposal.applier.d.ts.map +1 -0
- package/dist/lib/copilot/translation-proposal.applier.js +242 -0
- package/dist/lib/copilot/translation-proposal.provider.d.ts +114 -0
- package/dist/lib/copilot/translation-proposal.provider.d.ts.map +1 -0
- package/dist/lib/copilot/translation-proposal.provider.js +419 -0
- package/dist/lib/domain/errors/index.d.ts +4 -0
- package/dist/lib/domain/errors/index.d.ts.map +1 -0
- package/dist/lib/domain/errors/index.js +9 -0
- package/dist/lib/domain/errors/invalid-locale-set.error.d.ts +10 -0
- package/dist/lib/domain/errors/invalid-locale-set.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/invalid-locale-set.error.js +16 -0
- package/dist/lib/domain/errors/invalid-locale.error.d.ts +10 -0
- package/dist/lib/domain/errors/invalid-locale.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/invalid-locale.error.js +16 -0
- package/dist/lib/domain/errors/unknown-locale.error.d.ts +11 -0
- package/dist/lib/domain/errors/unknown-locale.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/unknown-locale.error.js +18 -0
- package/dist/lib/domain/locale-policy.d.ts +42 -0
- package/dist/lib/domain/locale-policy.d.ts.map +1 -0
- package/dist/lib/domain/locale-policy.js +61 -0
- package/dist/lib/domain/value-objects/locale-set.d.ts +41 -0
- package/dist/lib/domain/value-objects/locale-set.d.ts.map +1 -0
- package/dist/lib/domain/value-objects/locale-set.js +89 -0
- package/dist/lib/domain/value-objects/locale.d.ts +55 -0
- package/dist/lib/domain/value-objects/locale.d.ts.map +1 -0
- package/dist/lib/domain/value-objects/locale.js +73 -0
- package/dist/lib/i18n.constants.d.ts +33 -0
- package/dist/lib/i18n.constants.d.ts.map +1 -0
- package/dist/lib/i18n.constants.js +89 -0
- package/dist/lib/i18n.module.d.ts +14 -0
- package/dist/lib/i18n.module.d.ts.map +1 -0
- package/dist/lib/i18n.module.js +82 -0
- package/dist/lib/insights/http/controllers/localization-coverage.controller.d.ts +23 -0
- package/dist/lib/insights/http/controllers/localization-coverage.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/localization-coverage.controller.js +46 -0
- package/dist/lib/insights/infrastructure/queries/localization-coverage.query.d.ts +59 -0
- package/dist/lib/insights/infrastructure/queries/localization-coverage.query.d.ts.map +1 -0
- package/dist/lib/insights/infrastructure/queries/localization-coverage.query.js +179 -0
- package/dist/lib/insights/types/i18n-insights-view.d.ts +83 -0
- package/dist/lib/insights/types/i18n-insights-view.d.ts.map +1 -0
- package/dist/lib/insights/types/i18n-insights-view.js +10 -0
- package/dist/lib/locales/controllers/list-locales.controller.d.ts +26 -0
- package/dist/lib/locales/controllers/list-locales.controller.d.ts.map +1 -0
- package/dist/lib/locales/controllers/list-locales.controller.js +48 -0
- package/dist/lib/locales/services/locale-registry.service.d.ts +26 -0
- package/dist/lib/locales/services/locale-registry.service.d.ts.map +1 -0
- package/dist/lib/locales/services/locale-registry.service.js +70 -0
- package/dist/lib/locales/services/orphaned-locale.checker.d.ts +60 -0
- package/dist/lib/locales/services/orphaned-locale.checker.d.ts.map +1 -0
- package/dist/lib/locales/services/orphaned-locale.checker.js +114 -0
- package/dist/lib/types/locale.d.ts +68 -0
- package/dist/lib/types/locale.d.ts.map +1 -0
- package/dist/lib/types/locale.js +8 -0
- package/dist/lib/utils/i18n-plugin.d.ts +41 -0
- package/dist/lib/utils/i18n-plugin.d.ts.map +1 -0
- package/dist/lib/utils/i18n-plugin.js +53 -0
- package/package.json +47 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Locale, type LocaleInput } from './locale';
|
|
2
|
+
/**
|
|
3
|
+
* The configured set of {@link Locale}s — the value object that owns the
|
|
4
|
+
* **collection invariants** no single locale can guard: a non-empty set,
|
|
5
|
+
* unique slugs, and **exactly one default**. Because construction guarantees a
|
|
6
|
+
* default member, {@link default} never returns undefined, and {@link remove}
|
|
7
|
+
* refuses to drop the default. Preserves config (display) order.
|
|
8
|
+
*/
|
|
9
|
+
export declare class LocaleSet {
|
|
10
|
+
private readonly locales;
|
|
11
|
+
private readonly bySlug;
|
|
12
|
+
private readonly _default;
|
|
13
|
+
private constructor();
|
|
14
|
+
/**
|
|
15
|
+
* Builds a {@link LocaleSet} from already-validated {@link Locale}s,
|
|
16
|
+
* enforcing the collection invariants ({@link InvalidLocaleSetError} on a
|
|
17
|
+
* violation).
|
|
18
|
+
*/
|
|
19
|
+
static create(locales: readonly Locale[]): LocaleSet;
|
|
20
|
+
/**
|
|
21
|
+
* Builds a {@link LocaleSet} from the plugin's raw config defs — validating
|
|
22
|
+
* each locale's format ({@link Locale.create}) then the collection. The one
|
|
23
|
+
* entry point the plugin factory uses, so every locale rule lives here.
|
|
24
|
+
*/
|
|
25
|
+
static fromDefs(defs: readonly LocaleInput[]): LocaleSet;
|
|
26
|
+
/** All configured locales, in config (display) order. */
|
|
27
|
+
all(): Locale[];
|
|
28
|
+
/** One locale by slug, or undefined. */
|
|
29
|
+
get(slug: string): Locale | undefined;
|
|
30
|
+
/** Whether `slug` names a configured locale. */
|
|
31
|
+
has(slug: string): boolean;
|
|
32
|
+
/** The default locale (exactly one exists). */
|
|
33
|
+
default(): Locale;
|
|
34
|
+
/**
|
|
35
|
+
* The set with `slug` removed — guarding that only a **member** can be
|
|
36
|
+
* removed and that the **default is never removed** (either throws
|
|
37
|
+
* {@link InvalidLocaleSetError}).
|
|
38
|
+
*/
|
|
39
|
+
remove(slug: string): LocaleSet;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=locale-set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-set.d.ts","sourceRoot":"","sources":["../../../../src/lib/domain/value-objects/locale-set.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEpD;;;;;;GAMG;AACH,qBAAa,SAAS;IAIE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO;IAUP;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS;IAwBpD;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS;IAIxD,yDAAyD;IACzD,GAAG,IAAI,MAAM,EAAE;IAIf,wCAAwC;IACxC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIrC,gDAAgD;IAChD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,+CAA+C;IAC/C,OAAO,IAAI,MAAM;IAIjB;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;CAgBlC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocaleSet = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
const locale_1 = require("./locale");
|
|
6
|
+
/**
|
|
7
|
+
* The configured set of {@link Locale}s — the value object that owns the
|
|
8
|
+
* **collection invariants** no single locale can guard: a non-empty set,
|
|
9
|
+
* unique slugs, and **exactly one default**. Because construction guarantees a
|
|
10
|
+
* default member, {@link default} never returns undefined, and {@link remove}
|
|
11
|
+
* refuses to drop the default. Preserves config (display) order.
|
|
12
|
+
*/
|
|
13
|
+
class LocaleSet {
|
|
14
|
+
locales;
|
|
15
|
+
bySlug;
|
|
16
|
+
_default;
|
|
17
|
+
constructor(locales) {
|
|
18
|
+
this.locales = locales;
|
|
19
|
+
this.bySlug = new Map(locales.map((locale) => [locale.slug, locale]));
|
|
20
|
+
// `create` guarantees exactly one; the guard is for type flow.
|
|
21
|
+
const fallback = locales.find((locale) => locale.isDefault);
|
|
22
|
+
if (!fallback) {
|
|
23
|
+
throw new errors_1.InvalidLocaleSetError('A locale set has no default.');
|
|
24
|
+
}
|
|
25
|
+
this._default = fallback;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Builds a {@link LocaleSet} from already-validated {@link Locale}s,
|
|
29
|
+
* enforcing the collection invariants ({@link InvalidLocaleSetError} on a
|
|
30
|
+
* violation).
|
|
31
|
+
*/
|
|
32
|
+
static create(locales) {
|
|
33
|
+
if (locales.length === 0) {
|
|
34
|
+
throw new errors_1.InvalidLocaleSetError('A locale set requires at least one locale.');
|
|
35
|
+
}
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
for (const locale of locales) {
|
|
38
|
+
if (seen.has(locale.slug)) {
|
|
39
|
+
throw new errors_1.InvalidLocaleSetError(`Duplicate locale slug "${locale.slug}".`);
|
|
40
|
+
}
|
|
41
|
+
seen.add(locale.slug);
|
|
42
|
+
}
|
|
43
|
+
const defaults = locales.filter((locale) => locale.isDefault);
|
|
44
|
+
if (defaults.length !== 1) {
|
|
45
|
+
throw new errors_1.InvalidLocaleSetError(`Exactly one locale must set isDefault (got ${defaults.length}).`);
|
|
46
|
+
}
|
|
47
|
+
return new LocaleSet(locales);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds a {@link LocaleSet} from the plugin's raw config defs — validating
|
|
51
|
+
* each locale's format ({@link Locale.create}) then the collection. The one
|
|
52
|
+
* entry point the plugin factory uses, so every locale rule lives here.
|
|
53
|
+
*/
|
|
54
|
+
static fromDefs(defs) {
|
|
55
|
+
return LocaleSet.create(defs.map((def) => locale_1.Locale.create(def)));
|
|
56
|
+
}
|
|
57
|
+
/** All configured locales, in config (display) order. */
|
|
58
|
+
all() {
|
|
59
|
+
return [...this.locales];
|
|
60
|
+
}
|
|
61
|
+
/** One locale by slug, or undefined. */
|
|
62
|
+
get(slug) {
|
|
63
|
+
return this.bySlug.get(slug);
|
|
64
|
+
}
|
|
65
|
+
/** Whether `slug` names a configured locale. */
|
|
66
|
+
has(slug) {
|
|
67
|
+
return this.bySlug.has(slug);
|
|
68
|
+
}
|
|
69
|
+
/** The default locale (exactly one exists). */
|
|
70
|
+
default() {
|
|
71
|
+
return this._default;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The set with `slug` removed — guarding that only a **member** can be
|
|
75
|
+
* removed and that the **default is never removed** (either throws
|
|
76
|
+
* {@link InvalidLocaleSetError}).
|
|
77
|
+
*/
|
|
78
|
+
remove(slug) {
|
|
79
|
+
const target = this.bySlug.get(slug);
|
|
80
|
+
if (!target) {
|
|
81
|
+
throw new errors_1.InvalidLocaleSetError(`Locale "${slug}" is not a member.`);
|
|
82
|
+
}
|
|
83
|
+
if (target.isDefault) {
|
|
84
|
+
throw new errors_1.InvalidLocaleSetError(`Cannot remove the default locale "${slug}".`);
|
|
85
|
+
}
|
|
86
|
+
return LocaleSet.create(this.locales.filter((locale) => locale.slug !== slug));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.LocaleSet = LocaleSet;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type LocaleDir } from '../../i18n.constants';
|
|
2
|
+
/** The primitive shape a {@link Locale} is built from (the plugin's config). */
|
|
3
|
+
export interface LocaleInput {
|
|
4
|
+
/** Machine slug stored on entry rows (e.g. `en`, `de`, `pt-br`). */
|
|
5
|
+
slug: string;
|
|
6
|
+
/** Human display name (e.g. "English"). */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Whether this is the default locale. */
|
|
9
|
+
isDefault?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Text direction of content written in this locale. Optional in config —
|
|
12
|
+
* omitted, it is **inferred from the slug** (`ar`, `he`, `fa`, … → `rtl`),
|
|
13
|
+
* so a host adding an RTL language gets the right answer without saying so.
|
|
14
|
+
* Declare it to override the inference (a transliterated variant, say).
|
|
15
|
+
*/
|
|
16
|
+
dir?: LocaleDir;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A single configured locale — the value object that owns the **slug format
|
|
20
|
+
* rule** (the lowercase BCP-47-ish shape that used to live inline on the
|
|
21
|
+
* plugin factory) and a non-blank display name. Constructing one guarantees a
|
|
22
|
+
* well-formed locale, so every other i18n path can handle a `Locale` instead
|
|
23
|
+
* of a raw string it must re-validate.
|
|
24
|
+
*/
|
|
25
|
+
export declare class Locale {
|
|
26
|
+
private readonly _slug;
|
|
27
|
+
private readonly _name;
|
|
28
|
+
private readonly _isDefault;
|
|
29
|
+
private readonly _dir;
|
|
30
|
+
private constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Builds a {@link Locale}, rejecting a malformed slug, a blank name, or a
|
|
33
|
+
* `dir` that is neither `ltr` nor `rtl` with {@link InvalidLocaleError}
|
|
34
|
+
* (the plugin factory surfaces it at construction, request paths as HTTP
|
|
35
|
+
* 400). An omitted `dir` is inferred from the slug rather than assumed
|
|
36
|
+
* `ltr`, so configuring an RTL language is one line and not a silent
|
|
37
|
+
* accessibility failure.
|
|
38
|
+
*/
|
|
39
|
+
static create(input: LocaleInput): Locale;
|
|
40
|
+
/** The validated locale slug. */
|
|
41
|
+
get slug(): string;
|
|
42
|
+
/** The display name. */
|
|
43
|
+
get name(): string;
|
|
44
|
+
/** Whether this is the default locale. */
|
|
45
|
+
get isDefault(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Text direction of content in this locale — always resolved (declared or
|
|
48
|
+
* inferred), never undefined, so a consumer can set `dir` unconditionally
|
|
49
|
+
* instead of guessing.
|
|
50
|
+
*/
|
|
51
|
+
get dir(): LocaleDir;
|
|
52
|
+
/** Structural equality on the slug. */
|
|
53
|
+
equals(other: Locale): boolean;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=locale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../../../src/lib/domain/value-objects/locale.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,SAAS,EACjB,MAAM,sBAAsB,CAAC;AAG9B,gFAAgF;AAChF,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,SAAS,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAAa,MAAM;IAEX,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJzB,OAAO;IAOP;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IA4BzC,iCAAiC;IACjC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,wBAAwB;IACxB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,0CAA0C;IAC1C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;OAIG;IACH,IAAI,GAAG,IAAI,SAAS,CAEnB;IAED,uCAAuC;IACvC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGjC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Locale = void 0;
|
|
4
|
+
const i18n_constants_1 = require("../../i18n.constants");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
/**
|
|
7
|
+
* A single configured locale — the value object that owns the **slug format
|
|
8
|
+
* rule** (the lowercase BCP-47-ish shape that used to live inline on the
|
|
9
|
+
* plugin factory) and a non-blank display name. Constructing one guarantees a
|
|
10
|
+
* well-formed locale, so every other i18n path can handle a `Locale` instead
|
|
11
|
+
* of a raw string it must re-validate.
|
|
12
|
+
*/
|
|
13
|
+
class Locale {
|
|
14
|
+
_slug;
|
|
15
|
+
_name;
|
|
16
|
+
_isDefault;
|
|
17
|
+
_dir;
|
|
18
|
+
constructor(_slug, _name, _isDefault, _dir) {
|
|
19
|
+
this._slug = _slug;
|
|
20
|
+
this._name = _name;
|
|
21
|
+
this._isDefault = _isDefault;
|
|
22
|
+
this._dir = _dir;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds a {@link Locale}, rejecting a malformed slug, a blank name, or a
|
|
26
|
+
* `dir` that is neither `ltr` nor `rtl` with {@link InvalidLocaleError}
|
|
27
|
+
* (the plugin factory surfaces it at construction, request paths as HTTP
|
|
28
|
+
* 400). An omitted `dir` is inferred from the slug rather than assumed
|
|
29
|
+
* `ltr`, so configuring an RTL language is one line and not a silent
|
|
30
|
+
* accessibility failure.
|
|
31
|
+
*/
|
|
32
|
+
static create(input) {
|
|
33
|
+
const { slug, name, isDefault, dir } = input;
|
|
34
|
+
if (!i18n_constants_1.LOCALE_SLUG_RE.test(slug) ||
|
|
35
|
+
slug.length > i18n_constants_1.LOCALE_SLUG_MAX_LENGTH) {
|
|
36
|
+
throw new errors_1.InvalidLocaleError(`Locale slug "${slug}" is invalid — lowercase 2-3 letter ` +
|
|
37
|
+
`primary tag with optional "-" subtags (e.g. "en", "pt-br").`);
|
|
38
|
+
}
|
|
39
|
+
if (!name.trim()) {
|
|
40
|
+
throw new errors_1.InvalidLocaleError(`Locale "${slug}" has an empty name.`);
|
|
41
|
+
}
|
|
42
|
+
if (dir !== undefined && !i18n_constants_1.LOCALE_DIR.includes(dir)) {
|
|
43
|
+
throw new errors_1.InvalidLocaleError(`Locale "${slug}" has an invalid dir "${dir}" — ` +
|
|
44
|
+
`expected ${i18n_constants_1.LOCALE_DIR.map((d) => `"${d}"`).join(' or ')}.`);
|
|
45
|
+
}
|
|
46
|
+
return new Locale(slug, name, isDefault ?? false, dir ?? (0, i18n_constants_1.inferLocaleDir)(slug));
|
|
47
|
+
}
|
|
48
|
+
/** The validated locale slug. */
|
|
49
|
+
get slug() {
|
|
50
|
+
return this._slug;
|
|
51
|
+
}
|
|
52
|
+
/** The display name. */
|
|
53
|
+
get name() {
|
|
54
|
+
return this._name;
|
|
55
|
+
}
|
|
56
|
+
/** Whether this is the default locale. */
|
|
57
|
+
get isDefault() {
|
|
58
|
+
return this._isDefault;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Text direction of content in this locale — always resolved (declared or
|
|
62
|
+
* inferred), never undefined, so a consumer can set `dir` unconditionally
|
|
63
|
+
* instead of guessing.
|
|
64
|
+
*/
|
|
65
|
+
get dir() {
|
|
66
|
+
return this._dir;
|
|
67
|
+
}
|
|
68
|
+
/** Structural equality on the slug. */
|
|
69
|
+
equals(other) {
|
|
70
|
+
return this._slug === other._slug;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.Locale = Locale;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Shared constants of the i18n server plugin. */
|
|
2
|
+
/**
|
|
3
|
+
* Valid locale slugs: lowercase 2–3 letter primary tag, optional `-`-joined
|
|
4
|
+
* alphanumeric subtags (`en`, `de`, `pt-br`, `zh-hans`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const LOCALE_SLUG_RE: RegExp;
|
|
7
|
+
/** Longest accepted locale slug (mirrors BCP-47's practical bound). */
|
|
8
|
+
export declare const LOCALE_SLUG_MAX_LENGTH = 35;
|
|
9
|
+
/** DI token for the validated {@link I18nPluginConfig}. */
|
|
10
|
+
export declare const I18N_CONFIG: unique symbol;
|
|
11
|
+
/** Cap on `groupIds` per locale-summary batch request. */
|
|
12
|
+
export declare const LOCALE_SUMMARY_MAX_GROUPS = 100;
|
|
13
|
+
/** The `localeFallback` value that widens a list to the default locale. */
|
|
14
|
+
export declare const LOCALE_FALLBACK_DEFAULT = "default";
|
|
15
|
+
/**
|
|
16
|
+
* The two text directions a locale can declare — the values of the HTML `dir`
|
|
17
|
+
* attribute a consumer sets on the editor surface, the preview, and the
|
|
18
|
+
* published page (WCAG 1.3.2 Meaningful Sequence, 3.1.2 Language of Parts).
|
|
19
|
+
*/
|
|
20
|
+
export declare const LOCALE_DIR: readonly ["ltr", "rtl"];
|
|
21
|
+
/** A configured locale's text direction. */
|
|
22
|
+
export type LocaleDir = (typeof LOCALE_DIR)[number];
|
|
23
|
+
/**
|
|
24
|
+
* The text direction implied by a locale slug, used when the host's config
|
|
25
|
+
* declares none — so adding `{ slug: 'ar', name: 'العربية' }` yields an RTL
|
|
26
|
+
* locale without the host having to know it, while an explicit `dir` always
|
|
27
|
+
* wins.
|
|
28
|
+
*
|
|
29
|
+
* An explicit script subtag decides first (`az-arab` → `rtl`, `sr-latn` →
|
|
30
|
+
* `ltr`); otherwise the primary language subtag does; otherwise `ltr`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function inferLocaleDir(slug: string): LocaleDir;
|
|
33
|
+
//# sourceMappingURL=i18n.constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.constants.d.ts","sourceRoot":"","sources":["../../src/lib/i18n.constants.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAElD;;;GAGG;AACH,eAAO,MAAM,cAAc,QAA8B,CAAC;AAE1D,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,2DAA2D;AAC3D,eAAO,MAAM,WAAW,eAAwB,CAAC;AAEjD,0DAA0D;AAC1D,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,YAAY,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,UAAU,yBAA0B,CAAC;AAElD,4CAA4C;AAC5C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAmDpD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAOtD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Shared constants of the i18n server plugin. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.LOCALE_DIR = exports.LOCALE_FALLBACK_DEFAULT = exports.LOCALE_SUMMARY_MAX_GROUPS = exports.I18N_CONFIG = exports.LOCALE_SLUG_MAX_LENGTH = exports.LOCALE_SLUG_RE = void 0;
|
|
5
|
+
exports.inferLocaleDir = inferLocaleDir;
|
|
6
|
+
/**
|
|
7
|
+
* Valid locale slugs: lowercase 2–3 letter primary tag, optional `-`-joined
|
|
8
|
+
* alphanumeric subtags (`en`, `de`, `pt-br`, `zh-hans`).
|
|
9
|
+
*/
|
|
10
|
+
exports.LOCALE_SLUG_RE = /^[a-z]{2,3}(-[a-z0-9]+)*$/;
|
|
11
|
+
/** Longest accepted locale slug (mirrors BCP-47's practical bound). */
|
|
12
|
+
exports.LOCALE_SLUG_MAX_LENGTH = 35;
|
|
13
|
+
/** DI token for the validated {@link I18nPluginConfig}. */
|
|
14
|
+
exports.I18N_CONFIG = Symbol('I18N_CONFIG');
|
|
15
|
+
/** Cap on `groupIds` per locale-summary batch request. */
|
|
16
|
+
exports.LOCALE_SUMMARY_MAX_GROUPS = 100;
|
|
17
|
+
/** The `localeFallback` value that widens a list to the default locale. */
|
|
18
|
+
exports.LOCALE_FALLBACK_DEFAULT = 'default';
|
|
19
|
+
/**
|
|
20
|
+
* The two text directions a locale can declare — the values of the HTML `dir`
|
|
21
|
+
* attribute a consumer sets on the editor surface, the preview, and the
|
|
22
|
+
* published page (WCAG 1.3.2 Meaningful Sequence, 3.1.2 Language of Parts).
|
|
23
|
+
*/
|
|
24
|
+
exports.LOCALE_DIR = ['ltr', 'rtl'];
|
|
25
|
+
/**
|
|
26
|
+
* Script subtags that decide direction outright, whichever language carries
|
|
27
|
+
* them — `sr-latn` is LTR and `az-arab` is RTL regardless of what the language
|
|
28
|
+
* alone would imply. Checked before {@link RTL_LANGUAGE_SUBTAGS}, since an
|
|
29
|
+
* explicit script is the more specific statement.
|
|
30
|
+
*/
|
|
31
|
+
const RTL_SCRIPT_SUBTAGS = new Set([
|
|
32
|
+
'adlm',
|
|
33
|
+
'arab',
|
|
34
|
+
'aran',
|
|
35
|
+
'hebr',
|
|
36
|
+
'nkoo',
|
|
37
|
+
'rohg',
|
|
38
|
+
'syrc',
|
|
39
|
+
'thaa',
|
|
40
|
+
'yezi'
|
|
41
|
+
]);
|
|
42
|
+
/** Script subtags that force LTR even on a language written RTL elsewhere. */
|
|
43
|
+
const LTR_SCRIPT_SUBTAGS = new Set([
|
|
44
|
+
'cyrl',
|
|
45
|
+
'latn',
|
|
46
|
+
'hans',
|
|
47
|
+
'hant'
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Primary language subtags written right-to-left. Deliberately a short,
|
|
51
|
+
* conservative list of the languages a CMS is realistically configured with,
|
|
52
|
+
* not a mirror of CLDR: it only supplies the **default** for a locale that
|
|
53
|
+
* declares no `dir`, and a host with an exotic script says so explicitly.
|
|
54
|
+
*/
|
|
55
|
+
const RTL_LANGUAGE_SUBTAGS = new Set([
|
|
56
|
+
'ar', // Arabic
|
|
57
|
+
'arc', // Aramaic
|
|
58
|
+
'ckb', // Central Kurdish
|
|
59
|
+
'dv', // Divehi
|
|
60
|
+
'fa', // Persian
|
|
61
|
+
'he', // Hebrew
|
|
62
|
+
'ks', // Kashmiri
|
|
63
|
+
'ku', // Kurdish
|
|
64
|
+
'nqo', // N'Ko
|
|
65
|
+
'ps', // Pashto
|
|
66
|
+
'sd', // Sindhi
|
|
67
|
+
'ug', // Uyghur
|
|
68
|
+
'ur', // Urdu
|
|
69
|
+
'yi' // Yiddish
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* The text direction implied by a locale slug, used when the host's config
|
|
73
|
+
* declares none — so adding `{ slug: 'ar', name: 'العربية' }` yields an RTL
|
|
74
|
+
* locale without the host having to know it, while an explicit `dir` always
|
|
75
|
+
* wins.
|
|
76
|
+
*
|
|
77
|
+
* An explicit script subtag decides first (`az-arab` → `rtl`, `sr-latn` →
|
|
78
|
+
* `ltr`); otherwise the primary language subtag does; otherwise `ltr`.
|
|
79
|
+
*/
|
|
80
|
+
function inferLocaleDir(slug) {
|
|
81
|
+
const parts = slug.toLowerCase().split('-');
|
|
82
|
+
for (const part of parts.slice(1)) {
|
|
83
|
+
if (RTL_SCRIPT_SUBTAGS.has(part))
|
|
84
|
+
return 'rtl';
|
|
85
|
+
if (LTR_SCRIPT_SUBTAGS.has(part))
|
|
86
|
+
return 'ltr';
|
|
87
|
+
}
|
|
88
|
+
return RTL_LANGUAGE_SUBTAGS.has(parts[0]) ? 'rtl' : 'ltr';
|
|
89
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { I18nPluginConfig } from './types/locale';
|
|
3
|
+
/**
|
|
4
|
+
* NestJS module of the i18n plugin. Registered **global** so its binding of
|
|
5
|
+
* content-server's `CONTENT_ENTRY_EXTENSION` port resolves inside the
|
|
6
|
+
* (equally global) content module without an explicit import — the same
|
|
7
|
+
* inversion as content binding identity's `CONTENT_CATALOG`, roles swapped:
|
|
8
|
+
* content declares the port, this plugin provides the implementation.
|
|
9
|
+
*/
|
|
10
|
+
export declare class I18nModule {
|
|
11
|
+
/** Creates the global dynamic module around a validated config. */
|
|
12
|
+
static forRoot(config: I18nPluginConfig): DynamicModule;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=i18n.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.module.d.ts","sourceRoot":"","sources":["../../src/lib/i18n.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAIvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAiBvD;;;;;;GAMG;AACH,qBACa,UAAU;IACnB,mEAAmE;IACnE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa;CAmD1D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var I18nModule_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.I18nModule = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const content_server_1 = require("@orthacms/content-server");
|
|
8
|
+
const copilot_server_1 = require("@orthacms/copilot-server");
|
|
9
|
+
const i18n_constants_1 = require("./i18n.constants");
|
|
10
|
+
const locale_registry_service_1 = require("./locales/services/locale-registry.service");
|
|
11
|
+
const orphaned_locale_checker_1 = require("./locales/services/orphaned-locale.checker");
|
|
12
|
+
const list_locales_controller_1 = require("./locales/controllers/list-locales.controller");
|
|
13
|
+
const entry_locale_extension_service_1 = require("./content/services/entry-locale-extension.service");
|
|
14
|
+
const locale_group_service_1 = require("./content/services/locale-group.service");
|
|
15
|
+
const get_entry_locales_controller_1 = require("./content/controllers/get-entry-locales.controller");
|
|
16
|
+
const locale_summary_controller_1 = require("./content/controllers/locale-summary.controller");
|
|
17
|
+
const localization_coverage_query_1 = require("./insights/infrastructure/queries/localization-coverage.query");
|
|
18
|
+
const localization_coverage_controller_1 = require("./insights/http/controllers/localization-coverage.controller");
|
|
19
|
+
const i18n_tool_provider_1 = require("./copilot/i18n-tool.provider");
|
|
20
|
+
const translation_proposal_provider_1 = require("./copilot/translation-proposal.provider");
|
|
21
|
+
const translation_proposal_applier_1 = require("./copilot/translation-proposal.applier");
|
|
22
|
+
/**
|
|
23
|
+
* NestJS module of the i18n plugin. Registered **global** so its binding of
|
|
24
|
+
* content-server's `CONTENT_ENTRY_EXTENSION` port resolves inside the
|
|
25
|
+
* (equally global) content module without an explicit import — the same
|
|
26
|
+
* inversion as content binding identity's `CONTENT_CATALOG`, roles swapped:
|
|
27
|
+
* content declares the port, this plugin provides the implementation.
|
|
28
|
+
*/
|
|
29
|
+
let I18nModule = I18nModule_1 = class I18nModule {
|
|
30
|
+
/** Creates the global dynamic module around a validated config. */
|
|
31
|
+
static forRoot(config) {
|
|
32
|
+
return {
|
|
33
|
+
module: I18nModule_1,
|
|
34
|
+
global: true,
|
|
35
|
+
controllers: [
|
|
36
|
+
list_locales_controller_1.ListLocalesController,
|
|
37
|
+
// Reads only: the per-entry locale panel and the records
|
|
38
|
+
// table's batched group summary. Sibling *creation* goes
|
|
39
|
+
// through `POST /api/content/:type` with a `localeGroupId`
|
|
40
|
+
// (the extension stamps the group), so this plugin owns no
|
|
41
|
+
// entry-write route.
|
|
42
|
+
get_entry_locales_controller_1.GetEntryLocalesController,
|
|
43
|
+
locale_summary_controller_1.LocaleSummaryController,
|
|
44
|
+
// The Insights read-model. Mounted under `insights/` with
|
|
45
|
+
// content's and media's, not this plugin's `i18n/` prefix —
|
|
46
|
+
// one card, one endpoint, grouped by what they are.
|
|
47
|
+
localization_coverage_controller_1.LocalizationCoverageController
|
|
48
|
+
],
|
|
49
|
+
providers: [
|
|
50
|
+
{ provide: i18n_constants_1.I18N_CONFIG, useValue: config },
|
|
51
|
+
locale_registry_service_1.LocaleRegistryService,
|
|
52
|
+
// Boot-time diff of the configured locale set against what the
|
|
53
|
+
// data actually holds — the one locale change (a removal) that
|
|
54
|
+
// otherwise fails silently.
|
|
55
|
+
orphaned_locale_checker_1.OrphanedLocaleChecker,
|
|
56
|
+
locale_group_service_1.LocaleGroupService,
|
|
57
|
+
localization_coverage_query_1.LocalizationCoverageQuery,
|
|
58
|
+
entry_locale_extension_service_1.EntryLocaleExtensionService,
|
|
59
|
+
{
|
|
60
|
+
provide: content_server_1.CONTENT_ENTRY_EXTENSION,
|
|
61
|
+
useExisting: entry_locale_extension_service_1.EntryLocaleExtensionService
|
|
62
|
+
},
|
|
63
|
+
// The copilot's locale tools. Both no-op when no copilot
|
|
64
|
+
// plugin is registered — the registrar injects the registry
|
|
65
|
+
// optionally.
|
|
66
|
+
i18n_tool_provider_1.I18nCopilotToolProvider,
|
|
67
|
+
translation_proposal_provider_1.TranslationProposalToolProvider,
|
|
68
|
+
// The appliers for the kinds those propose tools produce —
|
|
69
|
+
// one per kind, next to them on purpose: a missing applier
|
|
70
|
+
// surfaces only when a change is carried out.
|
|
71
|
+
translation_proposal_applier_1.TranslationProposalApplier,
|
|
72
|
+
translation_proposal_applier_1.BulkTranslationProposalApplier,
|
|
73
|
+
(0, copilot_server_1.copilotAppliersRegistrar)('i18n', translation_proposal_applier_1.TranslationProposalApplier, translation_proposal_applier_1.BulkTranslationProposalApplier)
|
|
74
|
+
],
|
|
75
|
+
exports: [content_server_1.CONTENT_ENTRY_EXTENSION, locale_registry_service_1.LocaleRegistryService]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
exports.I18nModule = I18nModule;
|
|
80
|
+
exports.I18nModule = I18nModule = I18nModule_1 = tslib_1.__decorate([
|
|
81
|
+
(0, common_1.Module)({})
|
|
82
|
+
], I18nModule);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { LocalizationCoverageQuery } from '../../infrastructure/queries/localization-coverage.query';
|
|
2
|
+
import type { I18nCoverageView } from '../../types/i18n-insights-view';
|
|
3
|
+
/**
|
|
4
|
+
* `GET /api/insights/i18n/coverage` — how much of the workspace's localized
|
|
5
|
+
* content exists in each configured locale, and how many records are fully
|
|
6
|
+
* translated, untranslated, or part-way.
|
|
7
|
+
*
|
|
8
|
+
* Mounted under `insights/` beside content's and media's widgets rather than
|
|
9
|
+
* under this plugin's own `i18n/` prefix: the Insights page's endpoints are
|
|
10
|
+
* grouped by what they are, and a reader looking for the card's data should
|
|
11
|
+
* find it next to the other cards'.
|
|
12
|
+
*
|
|
13
|
+
* Gated on `content:read` — it counts content, and a reader who may not see
|
|
14
|
+
* entries must not learn how many there are by counting the gaps. Takes no
|
|
15
|
+
* `?days=`: an untranslated record is untranslated regardless of when it was
|
|
16
|
+
* written, so a window could only hide part of the backlog.
|
|
17
|
+
*/
|
|
18
|
+
export declare class LocalizationCoverageController {
|
|
19
|
+
private readonly coverage;
|
|
20
|
+
constructor(coverage: LocalizationCoverageQuery);
|
|
21
|
+
localization(workspaceId: string): Promise<I18nCoverageView>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=localization-coverage.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localization-coverage.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/insights/http/controllers/localization-coverage.controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,qBAGa,8BAA8B;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,yBAAyB;IAGhE,YAAY,CACY,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,gBAAgB,CAAC;CAG/B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalizationCoverageController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
7
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
8
|
+
const localization_coverage_query_1 = require("../../infrastructure/queries/localization-coverage.query");
|
|
9
|
+
/**
|
|
10
|
+
* `GET /api/insights/i18n/coverage` — how much of the workspace's localized
|
|
11
|
+
* content exists in each configured locale, and how many records are fully
|
|
12
|
+
* translated, untranslated, or part-way.
|
|
13
|
+
*
|
|
14
|
+
* Mounted under `insights/` beside content's and media's widgets rather than
|
|
15
|
+
* under this plugin's own `i18n/` prefix: the Insights page's endpoints are
|
|
16
|
+
* grouped by what they are, and a reader looking for the card's data should
|
|
17
|
+
* find it next to the other cards'.
|
|
18
|
+
*
|
|
19
|
+
* Gated on `content:read` — it counts content, and a reader who may not see
|
|
20
|
+
* entries must not learn how many there are by counting the gaps. Takes no
|
|
21
|
+
* `?days=`: an untranslated record is untranslated regardless of when it was
|
|
22
|
+
* written, so a window could only hide part of the backlog.
|
|
23
|
+
*/
|
|
24
|
+
let LocalizationCoverageController = class LocalizationCoverageController {
|
|
25
|
+
coverage;
|
|
26
|
+
constructor(coverage) {
|
|
27
|
+
this.coverage = coverage;
|
|
28
|
+
}
|
|
29
|
+
localization(workspaceId) {
|
|
30
|
+
return this.coverage.coverage(workspaceId);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.LocalizationCoverageController = LocalizationCoverageController;
|
|
34
|
+
tslib_1.__decorate([
|
|
35
|
+
(0, common_1.Get)('coverage'),
|
|
36
|
+
tslib_1.__param(0, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
37
|
+
tslib_1.__metadata("design:type", Function),
|
|
38
|
+
tslib_1.__metadata("design:paramtypes", [String]),
|
|
39
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
40
|
+
], LocalizationCoverageController.prototype, "localization", null);
|
|
41
|
+
exports.LocalizationCoverageController = LocalizationCoverageController = tslib_1.__decorate([
|
|
42
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
43
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.CONTENT_READ),
|
|
44
|
+
(0, common_1.Controller)('insights/i18n'),
|
|
45
|
+
tslib_1.__metadata("design:paramtypes", [localization_coverage_query_1.LocalizationCoverageQuery])
|
|
46
|
+
], LocalizationCoverageController);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type Database } from '@orthacms/database';
|
|
2
|
+
import type { ContentTypeRegistry } from '@orthacms/content-server';
|
|
3
|
+
import { LocaleRegistryService } from '../../../locales/services/locale-registry.service';
|
|
4
|
+
import type { I18nCoverageView } from '../../types/i18n-insights-view';
|
|
5
|
+
/**
|
|
6
|
+
* Live read-model for the localization coverage widget.
|
|
7
|
+
*
|
|
8
|
+
* **This lives in i18n-server, not content-server, and that is not arbitrary.**
|
|
9
|
+
* Coverage is a question about a set the content plugin deliberately does not
|
|
10
|
+
* know: the *configured* locales. content-server owns the `locale` column's
|
|
11
|
+
* shape and nothing about what a locale means, so it can report which slugs
|
|
12
|
+
* happen to appear in the data but not which ones are missing — and "missing"
|
|
13
|
+
* is the entire widget. `LocaleRegistryService` is the source of that set, and
|
|
14
|
+
* it is here.
|
|
15
|
+
*
|
|
16
|
+
* Like content's own insights query this aggregates **on demand**, per type,
|
|
17
|
+
* with no projection: a localized collection is its own generated
|
|
18
|
+
* `content_<name>` table, so a workspace-wide answer is inherently a fan-out.
|
|
19
|
+
*/
|
|
20
|
+
export declare class LocalizationCoverageQuery {
|
|
21
|
+
private readonly db;
|
|
22
|
+
private readonly registry;
|
|
23
|
+
private readonly locales;
|
|
24
|
+
constructor(db: Database, registry: ContentTypeRegistry, locales: LocaleRegistryService);
|
|
25
|
+
/** Coverage across every localized content type in the workspace. */
|
|
26
|
+
coverage(workspaceId: string): Promise<I18nCoverageView>;
|
|
27
|
+
/**
|
|
28
|
+
* Records holding a row in each locale, for one type.
|
|
29
|
+
*
|
|
30
|
+
* `count(distinct locale_group_id)` rather than `count(*)`: the
|
|
31
|
+
* `(locale_group_id, locale)` unique index makes those equal in a healthy
|
|
32
|
+
* table, and the distinct keeps the figure honest against a paranoid type
|
|
33
|
+
* where the index is partial and a trashed row could otherwise be counted
|
|
34
|
+
* twice.
|
|
35
|
+
*/
|
|
36
|
+
private perLocale;
|
|
37
|
+
/**
|
|
38
|
+
* How many translation groups this type has, how many are complete, and how
|
|
39
|
+
* many sit in a single language.
|
|
40
|
+
*
|
|
41
|
+
* One grouped subquery folded by an outer aggregate — the alternative,
|
|
42
|
+
* reading one row per group and counting in JS, would pull the workspace's
|
|
43
|
+
* whole content set over the wire to produce three integers.
|
|
44
|
+
*/
|
|
45
|
+
private groupSpread;
|
|
46
|
+
/**
|
|
47
|
+
* Workspace scope, the soft-delete guard, **and** the configured locale set.
|
|
48
|
+
*
|
|
49
|
+
* The locale filter is what makes "complete" mean what it says. A row in a
|
|
50
|
+
* slug the host has since removed from its config is not coverage of
|
|
51
|
+
* anything, and left in it would push a group's distinct-locale count past
|
|
52
|
+
* the configured total — so a record could be over-covered and still not
|
|
53
|
+
* counted as complete. Filtering here also drops a group made entirely of
|
|
54
|
+
* such rows, which is the right answer rather than a record with zero
|
|
55
|
+
* languages.
|
|
56
|
+
*/
|
|
57
|
+
private configuredScope;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=localization-coverage.query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localization-coverage.query.d.ts","sourceRoot":"","sources":["../../../../../src/lib/insights/infrastructure/queries/localization-coverage.query.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAER,mBAAmB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAC;AAC1F,OAAO,KAAK,EAER,gBAAgB,EAEnB,MAAM,gCAAgC,CAAC;AAUxC;;;;;;;;;;;;;;GAcG;AACH,qBACa,yBAAyB;IAEZ,OAAO,CAAC,QAAQ,CAAC,EAAE;IACZ,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFW,EAAE,EAAE,QAAQ,EACL,QAAQ,EAAE,mBAAmB,EACtD,OAAO,EAAE,qBAAqB;IAGnD,qEAAqE;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwE9D;;;;;;;;OAQG;YACW,SAAS;IAqBvB;;;;;;;OAOG;YACW,WAAW;IAwCzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,eAAe;CAY1B"}
|