@orthacms/i18n-server 0.4.3 → 0.5.1
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/dist/lib/content/services/entry-locale-extension.service.d.ts +8 -5
- package/dist/lib/content/services/entry-locale-extension.service.d.ts.map +1 -1
- package/dist/lib/content/services/entry-locale-extension.service.js +22 -6
- package/dist/lib/copilot/translation-proposal.applier.d.ts.map +1 -1
- package/dist/lib/copilot/translation-proposal.applier.js +3 -2
- package/dist/lib/docs/describe-i18n-api.d.ts +19 -0
- package/dist/lib/docs/describe-i18n-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-i18n-api.js +114 -0
- package/dist/lib/docs/i18n-schemas.d.ts +28 -0
- package/dist/lib/docs/i18n-schemas.d.ts.map +1 -0
- package/dist/lib/docs/i18n-schemas.js +182 -0
- package/dist/lib/insights/docs/describe-i18n-insights-api.d.ts +15 -0
- package/dist/lib/insights/docs/describe-i18n-insights-api.d.ts.map +1 -0
- package/dist/lib/insights/docs/describe-i18n-insights-api.js +151 -0
- package/dist/lib/utils/i18n-plugin.d.ts.map +1 -1
- package/dist/lib/utils/i18n-plugin.js +18 -1
- package/package.json +10 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type SQL } from 'drizzle-orm';
|
|
2
|
-
import { type Database } from '@orthacms/database';
|
|
2
|
+
import { UnitOfWork, type Database } from '@orthacms/database';
|
|
3
3
|
import { EntryValidationService, RelationLinkService, type AnyContentType, type ContentEntryExtension, type EntryFilterExtension, type EntryScopeParams, type EntryTransaction, type EntryWriteContext, type EntryWriteFanout } from '@orthacms/content-server';
|
|
4
4
|
import { LocaleRegistryService } from '../../locales/services/locale-registry.service';
|
|
5
5
|
/**
|
|
@@ -30,7 +30,8 @@ export declare class EntryLocaleExtensionService implements ContentEntryExtensio
|
|
|
30
30
|
private readonly locales;
|
|
31
31
|
private readonly validation;
|
|
32
32
|
private readonly relations;
|
|
33
|
-
|
|
33
|
+
private readonly uow;
|
|
34
|
+
constructor(db: Database, locales: LocaleRegistryService, validation: EntryValidationService, relations: RelationLinkService, uow: UnitOfWork);
|
|
34
35
|
/** @inheritdoc */
|
|
35
36
|
listScope(type: AnyContentType, workspaceId: string, params: EntryScopeParams): SQL | undefined;
|
|
36
37
|
/**
|
|
@@ -45,9 +46,11 @@ export declare class EntryLocaleExtensionService implements ContentEntryExtensio
|
|
|
45
46
|
createColumns(type: AnyContentType, workspaceId: string, params: EntryScopeParams): Promise<Record<string, unknown>>;
|
|
46
47
|
/**
|
|
47
48
|
* Assert a translation group has ≥1 live row in this workspace, so a
|
|
48
|
-
* sibling attaches to a real group. Runs before the create
|
|
49
|
-
* the (benign) TOCTOU window is covered by the row staying
|
|
50
|
-
* `(locale_group_id, locale)` unique index still guarding
|
|
49
|
+
* sibling attaches to a real group. Runs before the create's own
|
|
50
|
+
* transaction — the (benign) TOCTOU window is covered by the row staying
|
|
51
|
+
* valid and the `(locale_group_id, locale)` unique index still guarding
|
|
52
|
+
* duplicates — but *inside* any enclosing unit of work, which is why it
|
|
53
|
+
* reads through {@link UnitOfWork.current} rather than the base pool.
|
|
51
54
|
*/
|
|
52
55
|
private assertGroupExists;
|
|
53
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-locale-extension.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/services/entry-locale-extension.service.ts"],"names":[],"mappings":"AAMA,OAAO,EAYH,KAAK,GAAG,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"entry-locale-extension.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/services/entry-locale-extension.service.ts"],"names":[],"mappings":"AAMA,OAAO,EAYH,KAAK,GAAG,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAkB,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACH,sBAAsB,EACtB,mBAAmB,EAMnB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAE1B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACxB,MAAM,0BAA0B,CAAC;AASlC,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AAuCvF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,2BAA4B,YAAW,qBAAqB;IAE/C,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAK3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBARe,EAAE,EAAE,QAAQ,EAC9B,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,sBAAsB,EAKlC,SAAS,EAAE,mBAAmB,EAC9B,GAAG,EAAE,UAAU;IAGpC,kBAAkB;IAClB,SAAS,CACL,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,GACzB,GAAG,GAAG,SAAS;IAmClB;;;;;;;;OAQG;IACG,aAAa,CACf,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgBnC;;;;;;;OAOG;YACW,iBAAiB;IAmC/B;;;;;;;;;;;;OAYG;IACG,WAAW,CACb,EAAE,EAAE,gBAAgB,EACpB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC;IAKhB,kBAAkB;IACZ,WAAW,CACb,EAAE,EAAE,gBAAgB,EACpB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,iBAAiB,GAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAmBrC,kBAAkB;IACZ,cAAc,CAChB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAkDxC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;;;;;;OAaG;YACW,mBAAmB;IAiMjC;;;;;;;;;;;;;;;;;;;;;OAqBG;YACW,yBAAyB;IAmEvC;;;;;;;;;OASG;YACW,uBAAuB;IA0CrC;;;;;;;;;;OAUG;YACW,gBAAgB;IA6C9B,kBAAkB;IAClB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,oBAAoB,GAAG,SAAS;IAcvE;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,iBAAiB;IAmE/B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,aAAa;CAoBxB"}
|
|
@@ -70,16 +70,18 @@ let EntryLocaleExtensionService = class EntryLocaleExtensionService {
|
|
|
70
70
|
locales;
|
|
71
71
|
validation;
|
|
72
72
|
relations;
|
|
73
|
+
uow;
|
|
73
74
|
constructor(db, locales, validation,
|
|
74
75
|
// Relation storage is content's, not ours: reading a link set and
|
|
75
76
|
// replacing one are the same operations the entries pipeline performs,
|
|
76
77
|
// and a second implementation here is how the join table's ordering
|
|
77
78
|
// and de-duplication rules would drift.
|
|
78
|
-
relations) {
|
|
79
|
+
relations, uow) {
|
|
79
80
|
this.db = db;
|
|
80
81
|
this.locales = locales;
|
|
81
82
|
this.validation = validation;
|
|
82
83
|
this.relations = relations;
|
|
84
|
+
this.uow = uow;
|
|
83
85
|
}
|
|
84
86
|
/** @inheritdoc */
|
|
85
87
|
listScope(type, workspaceId, params) {
|
|
@@ -127,13 +129,26 @@ let EntryLocaleExtensionService = class EntryLocaleExtensionService {
|
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* Assert a translation group has ≥1 live row in this workspace, so a
|
|
130
|
-
* sibling attaches to a real group. Runs before the create
|
|
131
|
-
* the (benign) TOCTOU window is covered by the row staying
|
|
132
|
-
* `(locale_group_id, locale)` unique index still guarding
|
|
132
|
+
* sibling attaches to a real group. Runs before the create's own
|
|
133
|
+
* transaction — the (benign) TOCTOU window is covered by the row staying
|
|
134
|
+
* valid and the `(locale_group_id, locale)` unique index still guarding
|
|
135
|
+
* duplicates — but *inside* any enclosing unit of work, which is why it
|
|
136
|
+
* reads through {@link UnitOfWork.current} rather than the base pool.
|
|
133
137
|
*/
|
|
134
138
|
async assertGroupExists(type, localeGroupId, workspaceId) {
|
|
135
139
|
const table = type.table;
|
|
136
|
-
|
|
140
|
+
// `uow.current()`, not `this.db` — the same distinction
|
|
141
|
+
// `EntryWriterService` draws for its relation-target probe. Outside a
|
|
142
|
+
// unit of work the two are one connection and an ordinary save is
|
|
143
|
+
// unaffected. Inside one they are not, and the difference decides
|
|
144
|
+
// whether a *group* can be built in a single transaction: a content
|
|
145
|
+
// import writes the `en` row and then its `de` sibling in one unit of
|
|
146
|
+
// work, and a probe on the base pool cannot see the uncommitted `en`
|
|
147
|
+
// row — so the sibling was refused with "no translation group" for a
|
|
148
|
+
// group created moments earlier in the same transaction, and an
|
|
149
|
+
// imported locale pair arrived as one row plus an error.
|
|
150
|
+
const [row] = await this.uow
|
|
151
|
+
.current()
|
|
137
152
|
.select({ one: (0, drizzle_orm_1.sql) `1` })
|
|
138
153
|
.from(type.table)
|
|
139
154
|
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['localeGroupId'], localeGroupId), (0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), ...(type.paranoid ? [(0, drizzle_orm_1.isNull)(table['deletedAt'])] : [])))
|
|
@@ -658,5 +673,6 @@ exports.EntryLocaleExtensionService = EntryLocaleExtensionService = tslib_1.__de
|
|
|
658
673
|
tslib_1.__param(0, (0, database_1.InjectDatabase)()),
|
|
659
674
|
tslib_1.__metadata("design:paramtypes", [Object, locale_registry_service_1.LocaleRegistryService,
|
|
660
675
|
content_server_1.EntryValidationService,
|
|
661
|
-
content_server_1.RelationLinkService
|
|
676
|
+
content_server_1.RelationLinkService,
|
|
677
|
+
database_1.UnitOfWork])
|
|
662
678
|
], EntryLocaleExtensionService);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-proposal.applier.d.ts","sourceRoot":"","sources":["../../../src/lib/copilot/translation-proposal.applier.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"translation-proposal.applier.d.ts","sourceRoot":"","sources":["../../../src/lib/copilot/translation-proposal.applier.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EACH,kBAAkB,EAElB,oBAAoB,EAIpB,KAAK,mBAAmB,EAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACR,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,cAAc,EACjB,MAAM,0BAA0B,CAAC;AAMlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBACa,0BAA2B,YAAW,eAAe;IAK1D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACL,OAAO,CAAC,QAAQ,CAAC,EAAE;IAPzC,QAAQ,CAAC,IAAI,yBAAyC;gBAIjC,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,oBAAoB,EACV,EAAE,EAAE,QAAQ;IAG7C,KAAK,CACP,KAAK,EAAE;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EACjE,KAAK,EAAE,aAAa,GACrB,OAAO,CAAC,mBAAmB,CAAC;CAkDlC;AA0DD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBACa,8BAA+B,YAAW,eAAe;IAK9D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACL,OAAO,CAAC,QAAQ,CAAC,EAAE;IAPzC,QAAQ,CAAC,IAAI,8BAAuC;gBAI/B,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,oBAAoB,EACV,EAAE,EAAE,QAAQ;IAG7C,KAAK,CACP,KAAK,EAAE;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EACjE,KAAK,EAAE,aAAa,GACrB,OAAO,CAAC,mBAAmB,CAAC;CA8FlC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BulkTranslationProposalApplier = exports.TranslationProposalApplier = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const copilot_server_1 = require("@orthacms/copilot-server");
|
|
5
6
|
const common_1 = require("@nestjs/common");
|
|
6
7
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
7
8
|
const database_1 = require("@orthacms/database");
|
|
@@ -80,7 +81,7 @@ let TranslationProposalApplier = class TranslationProposalApplier {
|
|
|
80
81
|
}
|
|
81
82
|
const translated = (input.patch['values'] ?? {});
|
|
82
83
|
const inherited = await sharedValues(this.db, type, sourceId, actor.workspaceId);
|
|
83
|
-
const entry = await this.writer.create(type, { ...inherited, ...translated }, actor.workspaceId, undefined, locale, localeGroupId,
|
|
84
|
+
const entry = await this.writer.create(type, { ...inherited, ...translated }, actor.workspaceId, undefined, locale, localeGroupId, (0, copilot_server_1.proposalEventActor)(actor));
|
|
84
85
|
return {
|
|
85
86
|
entityId: entry.id,
|
|
86
87
|
detail: { typeName: type.name, locale, localeGroupId }
|
|
@@ -207,7 +208,7 @@ let BulkTranslationProposalApplier = class BulkTranslationProposalApplier {
|
|
|
207
208
|
// a stale copy of itself.
|
|
208
209
|
const inherited = await sharedValues(this.db, type, sourceId, actor.workspaceId);
|
|
209
210
|
const translated = (item.values ?? {});
|
|
210
|
-
const entry = await this.writer.create(type, { ...inherited, ...translated }, actor.workspaceId, undefined, locale, localeGroupId,
|
|
211
|
+
const entry = await this.writer.create(type, { ...inherited, ...translated }, actor.workspaceId, undefined, locale, localeGroupId, (0, copilot_server_1.proposalEventActor)(actor));
|
|
211
212
|
created.push(entry.id);
|
|
212
213
|
locales.push(locale);
|
|
213
214
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The i18n plugin's pass over the host's OpenAPI document.
|
|
3
|
+
*
|
|
4
|
+
* Three read routes, all of them answering a hand-written `interface` that
|
|
5
|
+
* `@nestjs/swagger` cannot see (`packages/bootstrap/server/AGENTS.md` → "The
|
|
6
|
+
* response-schema gap"). This is the mechanism content-server already uses:
|
|
7
|
+
* plain schema objects written onto the finished document, no decorator and no
|
|
8
|
+
* runtime-referenceable class.
|
|
9
|
+
*
|
|
10
|
+
* Pure: it takes the document and the configured locale slugs, and touches only
|
|
11
|
+
* the paths this plugin owns.
|
|
12
|
+
*/
|
|
13
|
+
import type { OpenApiDocument } from '@orthacms/bootstrap-server';
|
|
14
|
+
/**
|
|
15
|
+
* Adds this plugin's schemas to `document` and attaches them to its own three
|
|
16
|
+
* operations.
|
|
17
|
+
*/
|
|
18
|
+
export declare function describeI18nApi(document: OpenApiDocument, localeSlugs: readonly string[]): void;
|
|
19
|
+
//# sourceMappingURL=describe-i18n-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe-i18n-api.d.ts","sourceRoot":"","sources":["../../../src/lib/docs/describe-i18n-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AA2HlE;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC/B,IAAI,CAgBN"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The i18n plugin's pass over the host's OpenAPI document.
|
|
4
|
+
*
|
|
5
|
+
* Three read routes, all of them answering a hand-written `interface` that
|
|
6
|
+
* `@nestjs/swagger` cannot see (`packages/bootstrap/server/AGENTS.md` → "The
|
|
7
|
+
* response-schema gap"). This is the mechanism content-server already uses:
|
|
8
|
+
* plain schema objects written onto the finished document, no decorator and no
|
|
9
|
+
* runtime-referenceable class.
|
|
10
|
+
*
|
|
11
|
+
* Pure: it takes the document and the configured locale slugs, and touches only
|
|
12
|
+
* the paths this plugin owns.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.describeI18nApi = describeI18nApi;
|
|
16
|
+
const i18n_schemas_1 = require("./i18n-schemas");
|
|
17
|
+
/**
|
|
18
|
+
* The locales route, matched on its own full suffix.
|
|
19
|
+
*
|
|
20
|
+
* `/i18n/locales` and not a looser `/i18n` prefix: the insights plugin serves
|
|
21
|
+
* `/api/insights/i18n/coverage`, which a pattern anchored on the bare segment
|
|
22
|
+
* would also match. It would fall out of the route table below and describe
|
|
23
|
+
* nothing — but only by luck, and the next route either plugin adds is where
|
|
24
|
+
* luck runs out.
|
|
25
|
+
*/
|
|
26
|
+
const LOCALES_ROUTE_RE = /\/i18n\/locales$/;
|
|
27
|
+
/** Matches `<prefix>/i18n/content/{typeName}<rest>`. */
|
|
28
|
+
const CONTENT_ROUTE_RE = /\/i18n\/content\/\{typeName\}(.*)$/;
|
|
29
|
+
/** Content-scoped routes, keyed by what follows `/i18n/content/{typeName}`. */
|
|
30
|
+
const CONTENT_ROUTES = {
|
|
31
|
+
'/{id}/locales': {
|
|
32
|
+
get: {
|
|
33
|
+
schema: 'I18nEntryLocalesView',
|
|
34
|
+
description: 'The entry’s translation group, one slot per configured locale.'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
'/locale-summary': {
|
|
38
|
+
post: {
|
|
39
|
+
schema: 'I18nLocaleSummaryView',
|
|
40
|
+
description: 'The requested groups and their live members. A read, so it answers 200 rather than a 201 claiming it created something.'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** The locales route's one operation. */
|
|
45
|
+
const LOCALES_OPERATION = {
|
|
46
|
+
schema: 'I18nLocalesView',
|
|
47
|
+
description: 'The configured locales, in display order.'
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Writes a success schema onto whichever 2xx key the scanner already emitted,
|
|
51
|
+
* so this never invents a status code the API does not return. `POST
|
|
52
|
+
* /locale-summary` carries `@HttpCode(200)`, which is why the key is read
|
|
53
|
+
* rather than assumed.
|
|
54
|
+
*/
|
|
55
|
+
function setSuccessResponse(operation, schema, description) {
|
|
56
|
+
const responses = operation.responses ?? {};
|
|
57
|
+
const key = Object.keys(responses).find((code) => /^2\d\d$/.test(code));
|
|
58
|
+
if (!key || key === '204') {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
responses[key] = {
|
|
62
|
+
description,
|
|
63
|
+
content: { 'application/json': { schema } }
|
|
64
|
+
};
|
|
65
|
+
operation.responses = responses;
|
|
66
|
+
}
|
|
67
|
+
/** Adds a documented failure response, leaving any existing one alone. */
|
|
68
|
+
function addErrorResponse(operation, code, description) {
|
|
69
|
+
const responses = operation.responses ?? {};
|
|
70
|
+
if (responses[code]) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
responses[code] = { description };
|
|
74
|
+
operation.responses = responses;
|
|
75
|
+
}
|
|
76
|
+
/** Applies one route table entry to a path item. */
|
|
77
|
+
function describeItem(item, byMethod, contentScoped) {
|
|
78
|
+
for (const [method, operation] of Object.entries(item)) {
|
|
79
|
+
const spec = byMethod[method];
|
|
80
|
+
if (!spec || !operation || typeof operation !== 'object') {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const typed = operation;
|
|
84
|
+
setSuccessResponse(typed, (0, i18n_schemas_1.ref)(spec.schema), spec.description);
|
|
85
|
+
if (contentScoped) {
|
|
86
|
+
// Both content routes resolve `:typeName` through the same
|
|
87
|
+
// `resolveI18nType`, which draws exactly this line: a name nobody
|
|
88
|
+
// registered is a missing resource, a registered type that is not
|
|
89
|
+
// localized is a caller bug.
|
|
90
|
+
addErrorResponse(typed, '400', 'The content type exists but is not localized (`i18n: false`), so it has no translation groups.');
|
|
91
|
+
addErrorResponse(typed, '404', 'Unknown content type — or, for the per-entry panel, no live entry with this id in the open workspace.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Adds this plugin's schemas to `document` and attaches them to its own three
|
|
97
|
+
* operations.
|
|
98
|
+
*/
|
|
99
|
+
function describeI18nApi(document, localeSlugs) {
|
|
100
|
+
document.components ??= {};
|
|
101
|
+
document.components.schemas ??= {};
|
|
102
|
+
Object.assign(document.components.schemas, (0, i18n_schemas_1.buildI18nSchemas)(localeSlugs));
|
|
103
|
+
for (const [route, item] of Object.entries(document.paths)) {
|
|
104
|
+
if (LOCALES_ROUTE_RE.test(route)) {
|
|
105
|
+
describeItem(item, { get: LOCALES_OPERATION }, false);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const match = CONTENT_ROUTE_RE.exec(route);
|
|
109
|
+
const byMethod = match ? CONTENT_ROUTES[match[1]] : undefined;
|
|
110
|
+
if (byMethod) {
|
|
111
|
+
describeItem(item, byMethod, true);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The response schemas of the i18n plugin's three read routes, as plain
|
|
3
|
+
* OpenAPI objects.
|
|
4
|
+
*
|
|
5
|
+
* They are built rather than reflected because the shapes they describe are
|
|
6
|
+
* TypeScript `interface`s (`LocalesView`, `EntryLocalesView`,
|
|
7
|
+
* `LocaleSummaryView`) — erased at compile time, invisible to
|
|
8
|
+
* `@nestjs/swagger`'s scanner, and impossible to decorate. See
|
|
9
|
+
* `packages/bootstrap/server/AGENTS.md` → "The response-schema gap".
|
|
10
|
+
*
|
|
11
|
+
* Pure: locale slugs in, schema objects out. No document, no Nest, no
|
|
12
|
+
* registry.
|
|
13
|
+
*/
|
|
14
|
+
/** A JSON Schema fragment, as it appears in the OpenAPI document. */
|
|
15
|
+
export type OpenApiSchema = Record<string, unknown>;
|
|
16
|
+
/** A `$ref` to one of the schemas registered by {@link buildI18nSchemas}. */
|
|
17
|
+
export declare function ref(name: string): OpenApiSchema;
|
|
18
|
+
/**
|
|
19
|
+
* The i18n plugin's response schemas, keyed by component name.
|
|
20
|
+
*
|
|
21
|
+
* `locale` / `slug` carry the **configured** slugs as an enum, which is a fact
|
|
22
|
+
* about every response rather than a guess: the locales route maps the
|
|
23
|
+
* registry, the per-entry panel iterates the configured set, and the batch
|
|
24
|
+
* summary drops any row whose slug the host no longer declares. A locale the
|
|
25
|
+
* config does not name cannot appear in any of the three.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildI18nSchemas(localeSlugs: readonly string[]): Record<string, OpenApiSchema>;
|
|
28
|
+
//# sourceMappingURL=i18n-schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-schemas.d.ts","sourceRoot":"","sources":["../../../src/lib/docs/i18n-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,qEAAqE;AACrE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAE/C;AAqED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC5B,WAAW,EAAE,SAAS,MAAM,EAAE,GAC/B,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAoG/B"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The response schemas of the i18n plugin's three read routes, as plain
|
|
4
|
+
* OpenAPI objects.
|
|
5
|
+
*
|
|
6
|
+
* They are built rather than reflected because the shapes they describe are
|
|
7
|
+
* TypeScript `interface`s (`LocalesView`, `EntryLocalesView`,
|
|
8
|
+
* `LocaleSummaryView`) — erased at compile time, invisible to
|
|
9
|
+
* `@nestjs/swagger`'s scanner, and impossible to decorate. See
|
|
10
|
+
* `packages/bootstrap/server/AGENTS.md` → "The response-schema gap".
|
|
11
|
+
*
|
|
12
|
+
* Pure: locale slugs in, schema objects out. No document, no Nest, no
|
|
13
|
+
* registry.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.ref = ref;
|
|
17
|
+
exports.buildI18nSchemas = buildI18nSchemas;
|
|
18
|
+
const content_server_1 = require("@orthacms/content-server");
|
|
19
|
+
const i18n_constants_1 = require("../i18n.constants");
|
|
20
|
+
/** A `$ref` to one of the schemas registered by {@link buildI18nSchemas}. */
|
|
21
|
+
function ref(name) {
|
|
22
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The text direction of a locale. Always present on the wire even though it is
|
|
26
|
+
* optional in config: the registry resolves a declared or inferred value before
|
|
27
|
+
* anything is serialized, so no consumer has to decide for itself whether
|
|
28
|
+
* Arabic is right-to-left.
|
|
29
|
+
*/
|
|
30
|
+
const DIR_SCHEMA = {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: [...i18n_constants_1.LOCALE_DIR],
|
|
33
|
+
description: 'Resolved text direction for content in this locale — the HTML `dir` value to set alongside `lang`.'
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The publish state of one row. Present **only on a publishable content type**,
|
|
37
|
+
* which is why it appears in no `required` list: the services spread
|
|
38
|
+
* `status`/`publishedAt` in conditionally, so on a non-publishable type the keys
|
|
39
|
+
* are absent rather than null.
|
|
40
|
+
*/
|
|
41
|
+
const STATUS_SCHEMA = {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: Object.values(content_server_1.ENTRY_STATUS),
|
|
44
|
+
description: 'Publish state. Present on publishable content types only — absent, not null, on the rest.'
|
|
45
|
+
};
|
|
46
|
+
/** When this row last went live, or `null` if it never has. */
|
|
47
|
+
const PUBLISHED_AT_SCHEMA = {
|
|
48
|
+
type: 'string',
|
|
49
|
+
format: 'date-time',
|
|
50
|
+
nullable: true,
|
|
51
|
+
description: 'When this row last went live, or `null` if it never has. Publishable content types only. Paired with `status` it separates a never-published draft from one carrying unpublished edits over live content.'
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The group's row in one locale — the `entry` member of a locale panel item.
|
|
55
|
+
*
|
|
56
|
+
* **Inlined rather than a `$ref`**, because this is the one nullable object in
|
|
57
|
+
* the plugin's responses and OpenAPI 3.0 has no way to spell a nullable
|
|
58
|
+
* reference: every sibling of `$ref` is ignored, so `{ $ref, nullable: true }`
|
|
59
|
+
* reads as if it works and silently drops the null, and `nullable` beside a
|
|
60
|
+
* bare `allOf`/`oneOf` is adjunct to `type` and equally inert. `type: 'object'`
|
|
61
|
+
* with `nullable: true` is the form that actually says what the route returns.
|
|
62
|
+
*/
|
|
63
|
+
const ENTRY_ROW_SCHEMA = {
|
|
64
|
+
type: 'object',
|
|
65
|
+
nullable: true,
|
|
66
|
+
description: 'The group’s row in this locale, or `null` when it has not been translated yet. Enough to route to the row and render its state — not its values.',
|
|
67
|
+
properties: {
|
|
68
|
+
id: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
format: 'uuid',
|
|
71
|
+
description: 'The entry id of this locale’s row.'
|
|
72
|
+
},
|
|
73
|
+
status: STATUS_SCHEMA,
|
|
74
|
+
publishedAt: PUBLISHED_AT_SCHEMA,
|
|
75
|
+
updatedAt: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
format: 'date-time',
|
|
78
|
+
description: 'When this row was last written.'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
required: ['id', 'updatedAt']
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The i18n plugin's response schemas, keyed by component name.
|
|
85
|
+
*
|
|
86
|
+
* `locale` / `slug` carry the **configured** slugs as an enum, which is a fact
|
|
87
|
+
* about every response rather than a guess: the locales route maps the
|
|
88
|
+
* registry, the per-entry panel iterates the configured set, and the batch
|
|
89
|
+
* summary drops any row whose slug the host no longer declares. A locale the
|
|
90
|
+
* config does not name cannot appear in any of the three.
|
|
91
|
+
*/
|
|
92
|
+
function buildI18nSchemas(localeSlugs) {
|
|
93
|
+
const slugSchema = {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: [...localeSlugs],
|
|
96
|
+
description: 'A configured locale slug — a BCP-47 language tag, usable verbatim as an HTML `lang` value.'
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
I18nLocale: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
description: 'One configured locale.',
|
|
102
|
+
properties: {
|
|
103
|
+
slug: slugSchema,
|
|
104
|
+
name: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'Human display name, e.g. "English".'
|
|
107
|
+
},
|
|
108
|
+
isDefault: {
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
description: 'The locale applied when a request names none. Exactly one locale in the list sets it.'
|
|
111
|
+
},
|
|
112
|
+
dir: DIR_SCHEMA
|
|
113
|
+
},
|
|
114
|
+
required: ['slug', 'name', 'isDefault', 'dir']
|
|
115
|
+
},
|
|
116
|
+
I18nLocalesView: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
description: 'The configured locales, in display order. Every field is resolved server-side, so a client needs no locale table of its own.',
|
|
119
|
+
properties: {
|
|
120
|
+
items: { type: 'array', items: ref('I18nLocale') }
|
|
121
|
+
},
|
|
122
|
+
required: ['items']
|
|
123
|
+
},
|
|
124
|
+
I18nEntryLocaleItem: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
description: 'One configured locale’s slot in the translation group.',
|
|
127
|
+
properties: {
|
|
128
|
+
locale: slugSchema,
|
|
129
|
+
dir: DIR_SCHEMA,
|
|
130
|
+
isDefault: {
|
|
131
|
+
type: 'boolean',
|
|
132
|
+
description: 'Whether this is the configured default locale.'
|
|
133
|
+
},
|
|
134
|
+
entry: ENTRY_ROW_SCHEMA
|
|
135
|
+
},
|
|
136
|
+
required: ['locale', 'dir', 'isDefault', 'entry']
|
|
137
|
+
},
|
|
138
|
+
I18nEntryLocalesView: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
description: 'The locale panel of one entry: every configured locale, in config order, with the group’s row in it or `null`.',
|
|
141
|
+
properties: {
|
|
142
|
+
localeGroupId: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
format: 'uuid',
|
|
145
|
+
description: 'The translation group this entry belongs to — the stable identity of the record across languages.'
|
|
146
|
+
},
|
|
147
|
+
items: { type: 'array', items: ref('I18nEntryLocaleItem') }
|
|
148
|
+
},
|
|
149
|
+
required: ['localeGroupId', 'items']
|
|
150
|
+
},
|
|
151
|
+
I18nLocaleSummaryItem: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
description: 'One live member of a translation group.',
|
|
154
|
+
properties: {
|
|
155
|
+
locale: slugSchema,
|
|
156
|
+
entryId: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
format: 'uuid',
|
|
159
|
+
description: 'The member row’s entry id.'
|
|
160
|
+
},
|
|
161
|
+
status: STATUS_SCHEMA,
|
|
162
|
+
publishedAt: PUBLISHED_AT_SCHEMA
|
|
163
|
+
},
|
|
164
|
+
required: ['locale', 'entryId']
|
|
165
|
+
},
|
|
166
|
+
I18nLocaleSummaryView: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
description: 'Per requested translation-group id, that group’s live members in config order.',
|
|
169
|
+
properties: {
|
|
170
|
+
groups: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
description: 'Keyed by the group ids the request asked for — **exactly** those, no more and no fewer. A group in another workspace and one that names nothing at all both come back as an empty array, so a caller learns nothing it did not already supply.',
|
|
173
|
+
additionalProperties: {
|
|
174
|
+
type: 'array',
|
|
175
|
+
items: ref('I18nLocaleSummaryItem')
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
required: ['groups']
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The localization-coverage widget's pass over the host's OpenAPI document.
|
|
3
|
+
*
|
|
4
|
+
* One route, one shape — and the shape needs saying, because its unit is not
|
|
5
|
+
* the obvious one. Every figure here counts **translation groups**, not rows:
|
|
6
|
+
* a localized entry is one row per language sharing a `locale_group_id`, so
|
|
7
|
+
* counting rows would report a workspace of 40 stories in 3 languages as 120
|
|
8
|
+
* things and make every percentage meaningless.
|
|
9
|
+
*
|
|
10
|
+
* Pure: it takes the document and mutates only this plugin's own operation.
|
|
11
|
+
*/
|
|
12
|
+
import type { OpenApiDocument } from '@orthacms/bootstrap-server';
|
|
13
|
+
/** Adds the coverage schema and describes the one operation that returns it. */
|
|
14
|
+
export declare function describeI18nInsightsApi(document: OpenApiDocument): void;
|
|
15
|
+
//# sourceMappingURL=describe-i18n-insights-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe-i18n-insights-api.d.ts","sourceRoot":"","sources":["../../../../src/lib/insights/docs/describe-i18n-insights-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AA2HlE,gFAAgF;AAChF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAkCvE"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The localization-coverage widget's pass over the host's OpenAPI document.
|
|
4
|
+
*
|
|
5
|
+
* One route, one shape — and the shape needs saying, because its unit is not
|
|
6
|
+
* the obvious one. Every figure here counts **translation groups**, not rows:
|
|
7
|
+
* a localized entry is one row per language sharing a `locale_group_id`, so
|
|
8
|
+
* counting rows would report a workspace of 40 stories in 3 languages as 120
|
|
9
|
+
* things and make every percentage meaningless.
|
|
10
|
+
*
|
|
11
|
+
* Pure: it takes the document and mutates only this plugin's own operation.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.describeI18nInsightsApi = describeI18nInsightsApi;
|
|
15
|
+
/**
|
|
16
|
+
* The four per-type figures, reused for the workspace envelope.
|
|
17
|
+
*
|
|
18
|
+
* `notLocalized` is a **subset** of `requiresLocalization`, not a second slice
|
|
19
|
+
* of it — a record in one language of four both has no translations and needs
|
|
20
|
+
* some. They are separate because they are separate jobs: starting a
|
|
21
|
+
* translation, and finishing one. A client that adds them double-counts.
|
|
22
|
+
*/
|
|
23
|
+
function coverageCounts() {
|
|
24
|
+
return {
|
|
25
|
+
records: {
|
|
26
|
+
type: 'integer',
|
|
27
|
+
description: 'Translation groups of this type.'
|
|
28
|
+
},
|
|
29
|
+
localized: {
|
|
30
|
+
type: 'integer',
|
|
31
|
+
description: 'Of those, the ones present in **every** configured locale.'
|
|
32
|
+
},
|
|
33
|
+
notLocalized: {
|
|
34
|
+
type: 'integer',
|
|
35
|
+
description: 'Of those, the ones present in exactly one locale. Always 0 in a single-locale deployment, where a record would otherwise be both fully localized and not localized at all.'
|
|
36
|
+
},
|
|
37
|
+
requiresLocalization: {
|
|
38
|
+
type: 'integer',
|
|
39
|
+
description: 'Of those, the ones missing at least one locale (`records - localized`). Includes every `notLocalized` one.'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** The coverage response. */
|
|
44
|
+
const COVERAGE = {
|
|
45
|
+
type: 'object',
|
|
46
|
+
description: 'Localization coverage across every `i18n: true` content type. The unit is a translation group, never a row.',
|
|
47
|
+
required: [
|
|
48
|
+
'locales',
|
|
49
|
+
'records',
|
|
50
|
+
'localized',
|
|
51
|
+
'notLocalized',
|
|
52
|
+
'requiresLocalization',
|
|
53
|
+
'types'
|
|
54
|
+
],
|
|
55
|
+
properties: {
|
|
56
|
+
locales: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
description: 'One entry per configured locale, in config order.',
|
|
59
|
+
items: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
required: [
|
|
62
|
+
'locale',
|
|
63
|
+
'name',
|
|
64
|
+
'isDefault',
|
|
65
|
+
'translated',
|
|
66
|
+
'missing'
|
|
67
|
+
],
|
|
68
|
+
properties: {
|
|
69
|
+
locale: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'The configured locale slug.',
|
|
72
|
+
example: 'de'
|
|
73
|
+
},
|
|
74
|
+
name: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Its display name, from the host’s config.'
|
|
77
|
+
},
|
|
78
|
+
isDefault: { type: 'boolean' },
|
|
79
|
+
translated: {
|
|
80
|
+
type: 'integer',
|
|
81
|
+
description: 'Records that have a row in this locale.'
|
|
82
|
+
},
|
|
83
|
+
missing: {
|
|
84
|
+
type: 'integer',
|
|
85
|
+
description: 'Records that do not.'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
...coverageCounts(),
|
|
91
|
+
types: {
|
|
92
|
+
type: 'array',
|
|
93
|
+
description: 'Per-type coverage, most records first. A type the workspace has never used is omitted rather than listed at zero — a permanently empty row would push the types that hold work further down.',
|
|
94
|
+
items: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
required: [
|
|
97
|
+
'name',
|
|
98
|
+
'label',
|
|
99
|
+
'records',
|
|
100
|
+
'localized',
|
|
101
|
+
'notLocalized',
|
|
102
|
+
'requiresLocalization'
|
|
103
|
+
],
|
|
104
|
+
properties: {
|
|
105
|
+
name: { type: 'string' },
|
|
106
|
+
label: { type: 'string' },
|
|
107
|
+
...coverageCounts()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Matches `<prefix>/insights/i18n/coverage`.
|
|
115
|
+
*
|
|
116
|
+
* The prefix is matched as segments carrying no `{`, so a nested route ending
|
|
117
|
+
* in the same segments is not described with this shape.
|
|
118
|
+
*/
|
|
119
|
+
const ROUTE_RE = /^(?:\/[^/{}]+)*\/insights\/i18n\/coverage$/;
|
|
120
|
+
/** Adds the coverage schema and describes the one operation that returns it. */
|
|
121
|
+
function describeI18nInsightsApi(document) {
|
|
122
|
+
document.components ??= {};
|
|
123
|
+
document.components.schemas ??= {};
|
|
124
|
+
document.components.schemas['InsightsI18nCoverage'] = COVERAGE;
|
|
125
|
+
for (const [route, item] of Object.entries(document.paths)) {
|
|
126
|
+
if (!ROUTE_RE.test(route)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const operation = item['get'];
|
|
130
|
+
if (!operation || typeof operation !== 'object') {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// Onto whichever 2xx key the scanner emitted — never a new one.
|
|
134
|
+
const responses = operation.responses ?? {};
|
|
135
|
+
const key = Object.keys(responses).find((code) => /^2\d\d$/.test(code));
|
|
136
|
+
if (!key || key === '204') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
responses[key] = {
|
|
140
|
+
description: 'Coverage per locale and per localized content type, counted in translation groups.',
|
|
141
|
+
content: {
|
|
142
|
+
'application/json': {
|
|
143
|
+
schema: {
|
|
144
|
+
$ref: '#/components/schemas/InsightsI18nCoverage'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
operation.responses = responses;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-plugin.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/i18n-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"i18n-plugin.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/i18n-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAMxD;;;GAGG;AACH,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACtD,0CAA0C;IAC1C,UAAU,EAAE,gBAAgB,CAAC;CAChC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,gBAAgB,GACzB,oBAAoB,CAyBtB"}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.I18nServerPlugin = I18nServerPlugin;
|
|
4
4
|
const locale_set_1 = require("../domain/value-objects/locale-set");
|
|
5
|
+
const describe_i18n_api_1 = require("../docs/describe-i18n-api");
|
|
5
6
|
const i18n_module_1 = require("../i18n.module");
|
|
7
|
+
const describe_i18n_insights_api_1 = require("../insights/docs/describe-i18n-insights-api");
|
|
6
8
|
/**
|
|
7
9
|
* Validate the locale config **eagerly** (like `ContentPlugin`'s registry):
|
|
8
10
|
* a misconfigured host fails at construction — before boot, before the first
|
|
@@ -48,6 +50,21 @@ function I18nServerPlugin(config) {
|
|
|
48
50
|
return {
|
|
49
51
|
name: 'i18n',
|
|
50
52
|
module: i18n_module_1.I18nModule.forRoot(config),
|
|
51
|
-
i18nConfig: config
|
|
53
|
+
i18nConfig: config,
|
|
54
|
+
// The three read routes answer plain `interface`s, which the OpenAPI
|
|
55
|
+
// scanner cannot see and ADR-0003 would not let us decorate anyway.
|
|
56
|
+
// The configured slugs go in with them: which locales exist is runtime
|
|
57
|
+
// data held right here, and it is the one thing that makes the
|
|
58
|
+
// described `locale` fields exact rather than "some string".
|
|
59
|
+
docs: {
|
|
60
|
+
decorate: (document) => {
|
|
61
|
+
(0, describe_i18n_api_1.describeI18nApi)(document, config.locales.map((locale) => locale.slug));
|
|
62
|
+
// `/insights/i18n/coverage` is this plugin's route too, but it
|
|
63
|
+
// belongs to the Insights surface — a different tag, a
|
|
64
|
+
// different audience — so it is described from its own module
|
|
65
|
+
// rather than folded into the locale/content pass above.
|
|
66
|
+
(0, describe_i18n_insights_api_1.describeI18nInsightsApi)(document);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
52
69
|
};
|
|
53
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orthacms/i18n-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "@orthacms/i18n-server — part of Ortha CMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/ortha-source/ortha-cms/tree/main/packages/i18n/server",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@nestjs/common": "^11.0.0",
|
|
29
29
|
"@nestjs/swagger": "^11.4.6",
|
|
30
|
-
"@orthacms/bootstrap-server": "^0.
|
|
31
|
-
"@orthacms/content-server": "^0.
|
|
32
|
-
"@orthacms/copilot-domain": "^0.
|
|
33
|
-
"@orthacms/copilot-server": "^0.
|
|
34
|
-
"@orthacms/database": "^0.
|
|
35
|
-
"@orthacms/identity-server": "^0.
|
|
36
|
-
"@orthacms/tools-server": "^0.
|
|
37
|
-
"@orthacms/utils-server": "^0.
|
|
38
|
-
"@orthacms/workspaces-server": "^0.
|
|
30
|
+
"@orthacms/bootstrap-server": "^0.5.1",
|
|
31
|
+
"@orthacms/content-server": "^0.5.1",
|
|
32
|
+
"@orthacms/copilot-domain": "^0.5.1",
|
|
33
|
+
"@orthacms/copilot-server": "^0.5.1",
|
|
34
|
+
"@orthacms/database": "^0.5.1",
|
|
35
|
+
"@orthacms/identity-server": "^0.5.1",
|
|
36
|
+
"@orthacms/tools-server": "^0.5.1",
|
|
37
|
+
"@orthacms/utils-server": "^0.5.1",
|
|
38
|
+
"@orthacms/workspaces-server": "^0.5.1",
|
|
39
39
|
"class-transformer": "^0.5.1",
|
|
40
40
|
"class-validator": "^0.15.1",
|
|
41
41
|
"drizzle-orm": "^0.45.0",
|