@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ortha CMS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Public API of @orthacms/i18n-server. */
|
|
2
|
+
export { I18nServerPlugin } from './lib/utils/i18n-plugin';
|
|
3
|
+
export type { I18nServerPluginType } from './lib/utils/i18n-plugin';
|
|
4
|
+
export type { I18nPluginConfig, LocaleDef, LocaleDir, OrphanedLocalePolicy } from './lib/types/locale';
|
|
5
|
+
export { LOCALE_DIR, inferLocaleDir } from './lib/i18n.constants';
|
|
6
|
+
export { OrphanedLocaleChecker } from './lib/locales/services/orphaned-locale.checker';
|
|
7
|
+
export type { OrphanedLocaleReport } from './lib/locales/services/orphaned-locale.checker';
|
|
8
|
+
export { I18nModule } from './lib/i18n.module';
|
|
9
|
+
export { LocaleRegistryService } from './lib/locales/services/locale-registry.service';
|
|
10
|
+
export type { LocalesView } from './lib/locales/controllers/list-locales.controller';
|
|
11
|
+
export type { EntryLocaleItem, EntryLocalesView, LocaleSummaryItem, LocaleSummaryView } from './lib/content/services/locale-group.service';
|
|
12
|
+
export type { ContentTypeCoverageView, I18nCoverageView, LocaleCoverageView } from './lib/insights/types/i18n-insights-view';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EACR,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,oBAAoB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,gDAAgD,CAAC;AAE3F,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AACvF,YAAY,EAAE,WAAW,EAAE,MAAM,mDAAmD,CAAC;AACrF,YAAY,EACR,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACpB,MAAM,6CAA6C,CAAC;AACrD,YAAY,EACR,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,EACrB,MAAM,yCAAyC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Public API of @orthacms/i18n-server. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.LocaleRegistryService = exports.I18nModule = exports.OrphanedLocaleChecker = exports.inferLocaleDir = exports.LOCALE_DIR = exports.I18nServerPlugin = void 0;
|
|
5
|
+
var i18n_plugin_1 = require("./lib/utils/i18n-plugin");
|
|
6
|
+
Object.defineProperty(exports, "I18nServerPlugin", { enumerable: true, get: function () { return i18n_plugin_1.I18nServerPlugin; } });
|
|
7
|
+
var i18n_constants_1 = require("./lib/i18n.constants");
|
|
8
|
+
Object.defineProperty(exports, "LOCALE_DIR", { enumerable: true, get: function () { return i18n_constants_1.LOCALE_DIR; } });
|
|
9
|
+
Object.defineProperty(exports, "inferLocaleDir", { enumerable: true, get: function () { return i18n_constants_1.inferLocaleDir; } });
|
|
10
|
+
var orphaned_locale_checker_1 = require("./lib/locales/services/orphaned-locale.checker");
|
|
11
|
+
Object.defineProperty(exports, "OrphanedLocaleChecker", { enumerable: true, get: function () { return orphaned_locale_checker_1.OrphanedLocaleChecker; } });
|
|
12
|
+
var i18n_module_1 = require("./lib/i18n.module");
|
|
13
|
+
Object.defineProperty(exports, "I18nModule", { enumerable: true, get: function () { return i18n_module_1.I18nModule; } });
|
|
14
|
+
var locale_registry_service_1 = require("./lib/locales/services/locale-registry.service");
|
|
15
|
+
Object.defineProperty(exports, "LocaleRegistryService", { enumerable: true, get: function () { return locale_registry_service_1.LocaleRegistryService; } });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContentTypeRegistry } from '@orthacms/content-server';
|
|
2
|
+
import { LocaleGroupService, type EntryLocalesView } from '../services/locale-group.service';
|
|
3
|
+
/**
|
|
4
|
+
* `GET /api/i18n/content/:typeName/:id/locales` — the locale panel of one
|
|
5
|
+
* entry: every configured locale with the group's row in it (id, status,
|
|
6
|
+
* updatedAt) or null. Workspace-scoped like every entry read; gated on
|
|
7
|
+
* `content:read`.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GetEntryLocalesController {
|
|
10
|
+
private readonly registry;
|
|
11
|
+
private readonly groups;
|
|
12
|
+
constructor(registry: ContentTypeRegistry, groups: LocaleGroupService);
|
|
13
|
+
get(typeName: string, id: string, workspaceId: string): Promise<EntryLocalesView>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=get-entry-locales.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-entry-locales.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/controllers/get-entry-locales.controller.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACH,kBAAkB,EAClB,KAAK,gBAAgB,EACxB,MAAM,kCAAkC,CAAC;AAG1C;;;;;GAKG;AACH,qBAGa,yBAAyB;IAG9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,kBAAkB;IAI/C,GAAG,CACoB,QAAQ,EAAE,MAAM,EACP,EAAE,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,gBAAgB,CAAC;CAI/B"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetEntryLocalesController = 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 content_server_1 = require("@orthacms/content-server");
|
|
9
|
+
const locale_group_service_1 = require("../services/locale-group.service");
|
|
10
|
+
const resolve_type_1 = require("./resolve-type");
|
|
11
|
+
/**
|
|
12
|
+
* `GET /api/i18n/content/:typeName/:id/locales` — the locale panel of one
|
|
13
|
+
* entry: every configured locale with the group's row in it (id, status,
|
|
14
|
+
* updatedAt) or null. Workspace-scoped like every entry read; gated on
|
|
15
|
+
* `content:read`.
|
|
16
|
+
*/
|
|
17
|
+
let GetEntryLocalesController = class GetEntryLocalesController {
|
|
18
|
+
registry;
|
|
19
|
+
groups;
|
|
20
|
+
constructor(registry, groups) {
|
|
21
|
+
this.registry = registry;
|
|
22
|
+
this.groups = groups;
|
|
23
|
+
}
|
|
24
|
+
get(typeName, id, workspaceId) {
|
|
25
|
+
const type = (0, resolve_type_1.resolveI18nType)(this.registry, typeName);
|
|
26
|
+
return this.groups.entryLocales(type, id, workspaceId);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.GetEntryLocalesController = GetEntryLocalesController;
|
|
30
|
+
tslib_1.__decorate([
|
|
31
|
+
(0, common_1.Get)(':typeName/:id/locales'),
|
|
32
|
+
tslib_1.__param(0, (0, common_1.Param)('typeName')),
|
|
33
|
+
tslib_1.__param(1, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
34
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
35
|
+
tslib_1.__metadata("design:type", Function),
|
|
36
|
+
tslib_1.__metadata("design:paramtypes", [String, String, String]),
|
|
37
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
38
|
+
], GetEntryLocalesController.prototype, "get", null);
|
|
39
|
+
exports.GetEntryLocalesController = GetEntryLocalesController = tslib_1.__decorate([
|
|
40
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
41
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.CONTENT_READ),
|
|
42
|
+
(0, common_1.Controller)('i18n/content'),
|
|
43
|
+
tslib_1.__param(0, (0, content_server_1.InjectContentRegistry)()),
|
|
44
|
+
tslib_1.__metadata("design:paramtypes", [Function, locale_group_service_1.LocaleGroupService])
|
|
45
|
+
], GetEntryLocalesController);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ContentTypeRegistry } from '@orthacms/content-server';
|
|
2
|
+
import { LocaleSummaryDto } from '../dto/locale-summary.dto';
|
|
3
|
+
import { LocaleGroupService, type LocaleSummaryView } from '../services/locale-group.service';
|
|
4
|
+
/**
|
|
5
|
+
* `POST /api/i18n/content/:typeName/locale-summary` — the records table's
|
|
6
|
+
* batched read: per requested translation-group id, the group's live members
|
|
7
|
+
* with per-row status. Gated on `content:read`, workspace-scoped.
|
|
8
|
+
*
|
|
9
|
+
* **A POST that is a read**, because a page of group uuids outgrows a query
|
|
10
|
+
* string — so it answers `200`, not a `201` that would claim it created
|
|
11
|
+
* something.
|
|
12
|
+
*
|
|
13
|
+
* It still carries `OriginGuard`, unlike the reads that are shaped like reads.
|
|
14
|
+
* The guard keys off the **verb** a browser sees, not off what the handler
|
|
15
|
+
* happens to do with it: `POST` is the shape every cross-site form and
|
|
16
|
+
* `fetch` uses, this route is reachable with nothing but the session cookie,
|
|
17
|
+
* and "it only reads" is a property of today's handler that no test pins and
|
|
18
|
+
* a later edit could quietly retract. The guard is free for every legitimate
|
|
19
|
+
* caller — the admin sends its `Origin`, non-browser clients send none — so
|
|
20
|
+
* exempting it bought nothing and left the contract ("state-changing routes
|
|
21
|
+
* are origin-checked") disagreeing with the code. The body's shape is the
|
|
22
|
+
* batching decision; the guard is not part of it.
|
|
23
|
+
*/
|
|
24
|
+
export declare class LocaleSummaryController {
|
|
25
|
+
private readonly registry;
|
|
26
|
+
private readonly groups;
|
|
27
|
+
constructor(registry: ContentTypeRegistry, groups: LocaleGroupService);
|
|
28
|
+
summarize(typeName: string, body: LocaleSummaryDto, workspaceId: string): Promise<LocaleSummaryView>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=locale-summary.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-summary.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/controllers/locale-summary.controller.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACH,kBAAkB,EAClB,KAAK,iBAAiB,EACzB,MAAM,kCAAkC,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAGa,uBAAuB;IAG5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,kBAAkB;IAK/C,SAAS,CACc,QAAQ,EAAE,MAAM,EAC3B,IAAI,EAAE,gBAAgB,EACV,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,iBAAiB,CAAC;CAIhC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocaleSummaryController = 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 content_server_1 = require("@orthacms/content-server");
|
|
9
|
+
const locale_summary_dto_1 = require("../dto/locale-summary.dto");
|
|
10
|
+
const locale_group_service_1 = require("../services/locale-group.service");
|
|
11
|
+
const resolve_type_1 = require("./resolve-type");
|
|
12
|
+
/**
|
|
13
|
+
* `POST /api/i18n/content/:typeName/locale-summary` — the records table's
|
|
14
|
+
* batched read: per requested translation-group id, the group's live members
|
|
15
|
+
* with per-row status. Gated on `content:read`, workspace-scoped.
|
|
16
|
+
*
|
|
17
|
+
* **A POST that is a read**, because a page of group uuids outgrows a query
|
|
18
|
+
* string — so it answers `200`, not a `201` that would claim it created
|
|
19
|
+
* something.
|
|
20
|
+
*
|
|
21
|
+
* It still carries `OriginGuard`, unlike the reads that are shaped like reads.
|
|
22
|
+
* The guard keys off the **verb** a browser sees, not off what the handler
|
|
23
|
+
* happens to do with it: `POST` is the shape every cross-site form and
|
|
24
|
+
* `fetch` uses, this route is reachable with nothing but the session cookie,
|
|
25
|
+
* and "it only reads" is a property of today's handler that no test pins and
|
|
26
|
+
* a later edit could quietly retract. The guard is free for every legitimate
|
|
27
|
+
* caller — the admin sends its `Origin`, non-browser clients send none — so
|
|
28
|
+
* exempting it bought nothing and left the contract ("state-changing routes
|
|
29
|
+
* are origin-checked") disagreeing with the code. The body's shape is the
|
|
30
|
+
* batching decision; the guard is not part of it.
|
|
31
|
+
*/
|
|
32
|
+
let LocaleSummaryController = class LocaleSummaryController {
|
|
33
|
+
registry;
|
|
34
|
+
groups;
|
|
35
|
+
constructor(registry, groups) {
|
|
36
|
+
this.registry = registry;
|
|
37
|
+
this.groups = groups;
|
|
38
|
+
}
|
|
39
|
+
summarize(typeName, body, workspaceId) {
|
|
40
|
+
const type = (0, resolve_type_1.resolveI18nType)(this.registry, typeName);
|
|
41
|
+
return this.groups.summaries(type, body.groupIds, workspaceId);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
exports.LocaleSummaryController = LocaleSummaryController;
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, common_1.Post)(':typeName/locale-summary'),
|
|
47
|
+
(0, common_1.HttpCode)(200),
|
|
48
|
+
tslib_1.__param(0, (0, common_1.Param)('typeName')),
|
|
49
|
+
tslib_1.__param(1, (0, common_1.Body)()),
|
|
50
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
51
|
+
tslib_1.__metadata("design:type", Function),
|
|
52
|
+
tslib_1.__metadata("design:paramtypes", [String, locale_summary_dto_1.LocaleSummaryDto, String]),
|
|
53
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
54
|
+
], LocaleSummaryController.prototype, "summarize", null);
|
|
55
|
+
exports.LocaleSummaryController = LocaleSummaryController = tslib_1.__decorate([
|
|
56
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard, identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
57
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.CONTENT_READ),
|
|
58
|
+
(0, common_1.Controller)('i18n/content'),
|
|
59
|
+
tslib_1.__param(0, (0, content_server_1.InjectContentRegistry)()),
|
|
60
|
+
tslib_1.__metadata("design:paramtypes", [Function, locale_group_service_1.LocaleGroupService])
|
|
61
|
+
], LocaleSummaryController);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnyContentType, ContentTypeRegistry } from '@orthacms/content-server';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a route's `:typeName` to a registered **i18n** content type: 404
|
|
4
|
+
* for an unknown name (mirrors the content routes, no enumeration signal
|
|
5
|
+
* beyond what `/api/content-schema` already serves), 400 for a type that
|
|
6
|
+
* isn't localized (a caller bug, not a missing resource).
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveI18nType(registry: ContentTypeRegistry, typeName: string): AnyContentType;
|
|
9
|
+
//# sourceMappingURL=resolve-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-type.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/controllers/resolve-type.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,cAAc,EACd,mBAAmB,EACtB,MAAM,0BAA0B,CAAC;AAElC;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,MAAM,GACjB,cAAc,CAWhB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveI18nType = resolveI18nType;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a route's `:typeName` to a registered **i18n** content type: 404
|
|
7
|
+
* for an unknown name (mirrors the content routes, no enumeration signal
|
|
8
|
+
* beyond what `/api/content-schema` already serves), 400 for a type that
|
|
9
|
+
* isn't localized (a caller bug, not a missing resource).
|
|
10
|
+
*/
|
|
11
|
+
function resolveI18nType(registry, typeName) {
|
|
12
|
+
const type = registry.get(typeName);
|
|
13
|
+
if (!type) {
|
|
14
|
+
throw new common_1.NotFoundException(`Unknown content type "${typeName}".`);
|
|
15
|
+
}
|
|
16
|
+
if (!type.i18n) {
|
|
17
|
+
throw new common_1.BadRequestException(`Content type "${typeName}" is not localized.`);
|
|
18
|
+
}
|
|
19
|
+
return type;
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body for `POST /api/i18n/content/:typeName/locale-summary` — the records
|
|
3
|
+
* table's batched per-page read (POST, not GET: a page of uuids outgrows a
|
|
4
|
+
* query string). Capped so a request can't fan out unbounded.
|
|
5
|
+
*/
|
|
6
|
+
export declare class LocaleSummaryDto {
|
|
7
|
+
/** Translation-group ids to summarize (one page's worth). */
|
|
8
|
+
groupIds: string[];
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=locale-summary.dto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-summary.dto.d.ts","sourceRoot":"","sources":["../../../../src/lib/content/dto/locale-summary.dto.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,qBAAa,gBAAgB;IACzB,6DAA6D;IAW7D,QAAQ,EAAG,MAAM,EAAE,CAAC;CACvB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocaleSummaryDto = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
6
|
+
const class_validator_1 = require("class-validator");
|
|
7
|
+
const i18n_constants_1 = require("../../i18n.constants");
|
|
8
|
+
/**
|
|
9
|
+
* Body for `POST /api/i18n/content/:typeName/locale-summary` — the records
|
|
10
|
+
* table's batched per-page read (POST, not GET: a page of uuids outgrows a
|
|
11
|
+
* query string). Capped so a request can't fan out unbounded.
|
|
12
|
+
*/
|
|
13
|
+
class LocaleSummaryDto {
|
|
14
|
+
/** Translation-group ids to summarize (one page's worth). */
|
|
15
|
+
groupIds;
|
|
16
|
+
}
|
|
17
|
+
exports.LocaleSummaryDto = LocaleSummaryDto;
|
|
18
|
+
tslib_1.__decorate([
|
|
19
|
+
(0, swagger_1.ApiProperty)({
|
|
20
|
+
type: [String],
|
|
21
|
+
format: 'uuid',
|
|
22
|
+
maxItems: i18n_constants_1.LOCALE_SUMMARY_MAX_GROUPS,
|
|
23
|
+
example: ['c0a80101-7f3d-4c2b-9a1e-6d5b4c3a2f10'],
|
|
24
|
+
description: `Translation-group ids to summarize — one page's worth, capped at ${i18n_constants_1.LOCALE_SUMMARY_MAX_GROUPS}.`
|
|
25
|
+
}),
|
|
26
|
+
(0, class_validator_1.IsArray)(),
|
|
27
|
+
(0, class_validator_1.ArrayMaxSize)(i18n_constants_1.LOCALE_SUMMARY_MAX_GROUPS),
|
|
28
|
+
(0, class_validator_1.IsUUID)(undefined, { each: true }),
|
|
29
|
+
tslib_1.__metadata("design:type", Array)
|
|
30
|
+
], LocaleSummaryDto.prototype, "groupIds", void 0);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Database } from '@orthacms/database';
|
|
2
|
+
/**
|
|
3
|
+
* The minimal Drizzle executor the group lock needs: the root client or an
|
|
4
|
+
* open transaction (both expose `execute`). Mirrors
|
|
5
|
+
* `@orthacms/workspaces-server`'s `LockExecutor`, so content-server can pass
|
|
6
|
+
* the entry write's own `tx` straight through.
|
|
7
|
+
*/
|
|
8
|
+
export type LockExecutor = Pick<Database, 'execute'>;
|
|
9
|
+
/**
|
|
10
|
+
* Serializes writes across the members of one translation group, using a
|
|
11
|
+
* transaction-scoped Postgres advisory lock keyed on the `locale_group_id`.
|
|
12
|
+
* The lock auto-releases at commit/rollback.
|
|
13
|
+
*
|
|
14
|
+
* **Why an advisory lock rather than ordering the row locks.** The shared-field
|
|
15
|
+
* sync locks every sibling `FOR UPDATE`, and ordering *that* statement does not
|
|
16
|
+
* make the group deadlock-free: by the time the sync runs, the transaction
|
|
17
|
+
* already holds a row lock on the entry being saved, taken by the entries
|
|
18
|
+
* pipeline's own `UPDATE` before any i18n code ran. Two concurrent saves on two
|
|
19
|
+
* locales of one record therefore each hold a row the other is about to
|
|
20
|
+
* request, whatever order the sibling `SELECT … FOR UPDATE` uses — Postgres
|
|
21
|
+
* detects the cycle and aborts one with `deadlock detected` (SQLSTATE 40P01),
|
|
22
|
+
* which reaches the client as a 500 on an ordinary save.
|
|
23
|
+
*
|
|
24
|
+
* Taking this lock from the extension's `beforeWrite` hook — the first
|
|
25
|
+
* statement in the write transaction, ahead of the row `UPDATE` — imposes a
|
|
26
|
+
* total order on the group instead: the second saver waits at the lock, and by
|
|
27
|
+
* the time it proceeds the first has committed and released every row. Waiting
|
|
28
|
+
* is the correct outcome for two edits to one record; deadlocking is not.
|
|
29
|
+
*
|
|
30
|
+
* Exclusive rather than shared because every taker is a writer: a save reaches
|
|
31
|
+
* here only when it has shared columns or a syncing relation to propagate.
|
|
32
|
+
*/
|
|
33
|
+
export declare function lockLocaleGroup(executor: LockExecutor, localeGroupId: string): Promise<unknown>;
|
|
34
|
+
//# sourceMappingURL=locale-group-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-group-lock.d.ts","sourceRoot":"","sources":["../../../src/lib/content/locale-group-lock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAUrD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAC3B,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAIlB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lockLocaleGroup = lockLocaleGroup;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
/**
|
|
6
|
+
* A private lock class (the first `pg_advisory_xact_lock` key) namespacing the
|
|
7
|
+
* per-translation-group advisory lock below, so its hashed group-id key can't
|
|
8
|
+
* collide with any other advisory lock the app takes (workspace content `WK`,
|
|
9
|
+
* revision append `RE`, relation append `RL`). Arbitrary but fixed.
|
|
10
|
+
*/
|
|
11
|
+
const LOCALE_GROUP_LOCK_CLASS = 0x4c47; // 'LG'
|
|
12
|
+
/**
|
|
13
|
+
* Serializes writes across the members of one translation group, using a
|
|
14
|
+
* transaction-scoped Postgres advisory lock keyed on the `locale_group_id`.
|
|
15
|
+
* The lock auto-releases at commit/rollback.
|
|
16
|
+
*
|
|
17
|
+
* **Why an advisory lock rather than ordering the row locks.** The shared-field
|
|
18
|
+
* sync locks every sibling `FOR UPDATE`, and ordering *that* statement does not
|
|
19
|
+
* make the group deadlock-free: by the time the sync runs, the transaction
|
|
20
|
+
* already holds a row lock on the entry being saved, taken by the entries
|
|
21
|
+
* pipeline's own `UPDATE` before any i18n code ran. Two concurrent saves on two
|
|
22
|
+
* locales of one record therefore each hold a row the other is about to
|
|
23
|
+
* request, whatever order the sibling `SELECT … FOR UPDATE` uses — Postgres
|
|
24
|
+
* detects the cycle and aborts one with `deadlock detected` (SQLSTATE 40P01),
|
|
25
|
+
* which reaches the client as a 500 on an ordinary save.
|
|
26
|
+
*
|
|
27
|
+
* Taking this lock from the extension's `beforeWrite` hook — the first
|
|
28
|
+
* statement in the write transaction, ahead of the row `UPDATE` — imposes a
|
|
29
|
+
* total order on the group instead: the second saver waits at the lock, and by
|
|
30
|
+
* the time it proceeds the first has committed and released every row. Waiting
|
|
31
|
+
* is the correct outcome for two edits to one record; deadlocking is not.
|
|
32
|
+
*
|
|
33
|
+
* Exclusive rather than shared because every taker is a writer: a save reaches
|
|
34
|
+
* here only when it has shared columns or a syncing relation to propagate.
|
|
35
|
+
*/
|
|
36
|
+
function lockLocaleGroup(executor, localeGroupId) {
|
|
37
|
+
return executor.execute((0, drizzle_orm_1.sql) `select pg_advisory_xact_lock(${LOCALE_GROUP_LOCK_CLASS}, hashtext(${localeGroupId}))`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { type SQL } from 'drizzle-orm';
|
|
2
|
+
import { type Database } from '@orthacms/database';
|
|
3
|
+
import { EntryValidationService, RelationLinkService, type AnyContentType, type ContentEntryExtension, type EntryFilterExtension, type EntryScopeParams, type EntryTransaction, type EntryWriteContext, type EntryWriteFanout } from '@orthacms/content-server';
|
|
4
|
+
import { LocaleRegistryService } from '../../locales/services/locale-registry.service';
|
|
5
|
+
/**
|
|
6
|
+
* The i18n plugin's binding of content-server's `CONTENT_ENTRY_EXTENSION`
|
|
7
|
+
* port — where every locale *behavior* lives, so the content library itself
|
|
8
|
+
* stays locale-agnostic:
|
|
9
|
+
*
|
|
10
|
+
* - **List scoping** — the active-locale filter (strict; the default locale
|
|
11
|
+
* when the request names none), plus the relation picker's
|
|
12
|
+
* `localeFallback=default` union.
|
|
13
|
+
* - **Create stamping** — validates the requested slug and stamps the
|
|
14
|
+
* `locale` envelope column (the group id comes from the column default: a
|
|
15
|
+
* plain create starts its own translation group).
|
|
16
|
+
* - **Shared-field sync** — on update, non-`localized` field values propagate
|
|
17
|
+
* to every sibling row of the translation group, inside the same
|
|
18
|
+
* transaction; published siblings are re-validated so a draft edit can
|
|
19
|
+
* never silently invalidate live content. The rewritten siblings are handed
|
|
20
|
+
* back to the entries pipeline, which appends a **revision** for each — their
|
|
21
|
+
* values changed, so their history has to say so.
|
|
22
|
+
* - **Virtual filters** — `hasLocale` / `missingLocale` / `localeCount`,
|
|
23
|
+
* resolved to EXISTS / count subqueries over the group (ridden by the
|
|
24
|
+
* `(locale_group_id, locale)` unique index).
|
|
25
|
+
*
|
|
26
|
+
* Every method no-ops for non-i18n types, as the port contract requires.
|
|
27
|
+
*/
|
|
28
|
+
export declare class EntryLocaleExtensionService implements ContentEntryExtension {
|
|
29
|
+
private readonly db;
|
|
30
|
+
private readonly locales;
|
|
31
|
+
private readonly validation;
|
|
32
|
+
private readonly relations;
|
|
33
|
+
constructor(db: Database, locales: LocaleRegistryService, validation: EntryValidationService, relations: RelationLinkService);
|
|
34
|
+
/** @inheritdoc */
|
|
35
|
+
listScope(type: AnyContentType, workspaceId: string, params: EntryScopeParams): SQL | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* @inheritdoc
|
|
38
|
+
*
|
|
39
|
+
* Stamps the validated `locale` (defaulting when absent). When a
|
|
40
|
+
* `localeGroupId` is given the new row **joins that existing group** (a
|
|
41
|
+
* sibling translation) — verified to name a real group in the workspace
|
|
42
|
+
* first (else 404), so a typo can't spawn a stray one-row group. Absent →
|
|
43
|
+
* the column default (`gen_random_uuid()`) starts a fresh group.
|
|
44
|
+
*/
|
|
45
|
+
createColumns(type: AnyContentType, workspaceId: string, params: EntryScopeParams): Promise<Record<string, unknown>>;
|
|
46
|
+
/**
|
|
47
|
+
* Assert a translation group has ≥1 live row in this workspace, so a
|
|
48
|
+
* sibling attaches to a real group. Runs before the create transaction —
|
|
49
|
+
* the (benign) TOCTOU window is covered by the row staying valid and the
|
|
50
|
+
* `(locale_group_id, locale)` unique index still guarding duplicates.
|
|
51
|
+
*/
|
|
52
|
+
private assertGroupExists;
|
|
53
|
+
/**
|
|
54
|
+
* @inheritdoc
|
|
55
|
+
*
|
|
56
|
+
* Takes the translation group's advisory lock, ahead of every row lock the
|
|
57
|
+
* write will acquire. See {@link lockLocaleGroup} for why ordering the
|
|
58
|
+
* sibling `FOR UPDATE` cannot replace this: the edited row is already
|
|
59
|
+
* locked by the pipeline's own `UPDATE` before any i18n code runs, so two
|
|
60
|
+
* saves in one group invert their lock order no matter how the sync sorts
|
|
61
|
+
* its siblings.
|
|
62
|
+
*
|
|
63
|
+
* No group id means a create starting a fresh group — nothing to contend
|
|
64
|
+
* on, so nothing to lock.
|
|
65
|
+
*/
|
|
66
|
+
beforeWrite(tx: EntryTransaction, type: AnyContentType, params: EntryScopeParams): Promise<void>;
|
|
67
|
+
/** @inheritdoc */
|
|
68
|
+
afterUpdate(tx: EntryTransaction, type: AnyContentType, row: Record<string, unknown>, values: Record<string, unknown>, workspaceId: string, context: EntryWriteContext): Promise<Record<string, unknown>[]>;
|
|
69
|
+
/** @inheritdoc */
|
|
70
|
+
describeFanout(type: AnyContentType, entryId: string, values: Record<string, unknown>, workspaceId: string): Promise<EntryWriteFanout | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether this field's value reaches the row's siblings when it is saved —
|
|
73
|
+
* the predicate behind {@link describeFanout}, stated once against the same
|
|
74
|
+
* three rules {@link propagateToSiblings} applies.
|
|
75
|
+
*
|
|
76
|
+
* It is broader than {@link sharedColumns}, deliberately: that method
|
|
77
|
+
* answers "which columns copy across verbatim", because shared columns,
|
|
78
|
+
* mirrored FKs and join-backed link sets are *applied* by three different
|
|
79
|
+
* mechanisms. To a person being told what their change will do, the three
|
|
80
|
+
* are one fact — the value they set here lands over there.
|
|
81
|
+
*
|
|
82
|
+
* A **mirrored** relation counts even though each sibling ends up holding a
|
|
83
|
+
* different id: the sibling's link is being repointed at the record this
|
|
84
|
+
* save chose, which is a change to that row whatever id expresses it.
|
|
85
|
+
*/
|
|
86
|
+
private travelsToSiblings;
|
|
87
|
+
/**
|
|
88
|
+
* Push this row's state onto every sibling in its translation group, and
|
|
89
|
+
* return the rows actually rewritten (the pipeline appends a revision for
|
|
90
|
+
* each — their content moved in this transaction, and without a version
|
|
91
|
+
* their history would skip the change and a later restore would silently
|
|
92
|
+
* undo it).
|
|
93
|
+
*
|
|
94
|
+
* Three kinds of state travel, each computed once and applied per sibling:
|
|
95
|
+
* shared **columns** (identical everywhere), **mirrored** single-relation
|
|
96
|
+
* FKs (resolved into each sibling's own locale), and the **link sets** of
|
|
97
|
+
* join-backed relations. A sibling whose every one of those already matches
|
|
98
|
+
* is left completely untouched — that guard is what stops one save
|
|
99
|
+
* re-versioning the whole group.
|
|
100
|
+
*/
|
|
101
|
+
private propagateToSiblings;
|
|
102
|
+
/**
|
|
103
|
+
* Fill a **newly created** sibling's propagating relations from the group
|
|
104
|
+
* it just joined — the half of the sync that runs inward.
|
|
105
|
+
*
|
|
106
|
+
* A translation is created through the ordinary create route with a
|
|
107
|
+
* `localeGroupId`, and a create body carries no relation links at all: they
|
|
108
|
+
* are join-backed (never in the values bag) or per-locale (deliberately
|
|
109
|
+
* dropped by the client, since the source's id names another language's
|
|
110
|
+
* row). So without this a translation is born with none of the record's
|
|
111
|
+
* links, which is exactly the manual per-locale re-linking this sync
|
|
112
|
+
* exists to remove.
|
|
113
|
+
*
|
|
114
|
+
* The donor is one existing sibling — any of them agree on a shared link,
|
|
115
|
+
* and a mirrored one resolves through the same translation group whichever
|
|
116
|
+
* is asked — picked deterministically (the default locale where the group
|
|
117
|
+
* has it) so two creates never inherit from different rows.
|
|
118
|
+
*
|
|
119
|
+
* Columns it derives are also written back onto `row` **in place**: the
|
|
120
|
+
* caller snapshots that object as the new entry's first revision *after*
|
|
121
|
+
* this call, so a database-only write would leave version 1 describing
|
|
122
|
+
* something the row never held.
|
|
123
|
+
*/
|
|
124
|
+
private inheritRelationsFromGroup;
|
|
125
|
+
/**
|
|
126
|
+
* Per locale, the **mirrored** single-relation FK columns a sibling in that
|
|
127
|
+
* locale should hold: the source's linked record, resolved to that record's
|
|
128
|
+
* own translation in the sibling's language.
|
|
129
|
+
*
|
|
130
|
+
* An unresolvable link becomes `null` rather than being skipped. Leaving
|
|
131
|
+
* the sibling's existing value would keep it pointing at the *previous*
|
|
132
|
+
* record's translation — silently wrong data, which is worse than an empty
|
|
133
|
+
* field that the editor can see and the publish gate can block on.
|
|
134
|
+
*/
|
|
135
|
+
private mirroredColumnsByLocale;
|
|
136
|
+
/**
|
|
137
|
+
* Per join-backed relation field, per locale, the ordered link ids a
|
|
138
|
+
* sibling in that locale should hold.
|
|
139
|
+
*
|
|
140
|
+
* A **shared** relation hands every sibling the source's ids verbatim — the
|
|
141
|
+
* target has no locales, so there is one right row for the whole group. A
|
|
142
|
+
* **mirrored** one maps each id through its translation group into the
|
|
143
|
+
* sibling's language, preserving the source's order and dropping links
|
|
144
|
+
* whose target has no translation there (a content gap on the target, not a
|
|
145
|
+
* reason to fail this save).
|
|
146
|
+
*/
|
|
147
|
+
private linkSetsByLocale;
|
|
148
|
+
/** @inheritdoc */
|
|
149
|
+
filterExtension(type: AnyContentType): EntryFilterExtension | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Translate one virtual locale rule into SQL. All three fields quantify
|
|
152
|
+
* over the row's translation group via a self-EXISTS/count subquery,
|
|
153
|
+
* workspace-scoped, (on paranoid types) live-rows-only, and **restricted to
|
|
154
|
+
* the configured locale set** — the same restriction the coverage query
|
|
155
|
+
* applies (`configuredScope`).
|
|
156
|
+
*
|
|
157
|
+
* That last one is what keeps the records table and the Translation
|
|
158
|
+
* coverage card telling the same story. A row in a slug the host has since
|
|
159
|
+
* dropped is not coverage of anything: coverage filters it out, so leaving
|
|
160
|
+
* it in here made `localeCount` count higher than the number of configured
|
|
161
|
+
* locales, and a record the card reported as fully localized answered
|
|
162
|
+
* neither `localeCount eq 3` (the configured total) nor any `hasLocale`
|
|
163
|
+
* question consistent with it. Two views of one record, disagreeing.
|
|
164
|
+
*
|
|
165
|
+
* `missingLocale in [a, b]` is a `notExists` over the union, so it means
|
|
166
|
+
* "missing **all** of these", not "missing any" — the union has no matching
|
|
167
|
+
* sibling only when every named locale is absent. It is the useful reading
|
|
168
|
+
* for the records table ("show me what has none of my target languages");
|
|
169
|
+
* "missing any" is expressed as an `or` of `missingLocale eq` rules.
|
|
170
|
+
*/
|
|
171
|
+
private resolveLocaleRule;
|
|
172
|
+
/**
|
|
173
|
+
* The column bag of the fields whose value is **identical in every locale**,
|
|
174
|
+
* drawn from a save's coerced values — the part of the sync that is the
|
|
175
|
+
* same for every sibling.
|
|
176
|
+
*
|
|
177
|
+
* Scalars and media qualify unless marked `localized`. A relation qualifies
|
|
178
|
+
* only when it is column-backed *and* {@link RELATION_LOCALE_SYNC.Shared}:
|
|
179
|
+
* a join-backed relation has no column to copy (its links go through
|
|
180
|
+
* {@link linkSetsByLocale}), and a mirrored one stores a different id in
|
|
181
|
+
* every locale by design (see {@link mirroredColumnsByLocale}), so copying
|
|
182
|
+
* one verbatim would be exactly the cross-locale link the writer rejects.
|
|
183
|
+
*
|
|
184
|
+
* `null` when the type has no such field at all.
|
|
185
|
+
*/
|
|
186
|
+
private sharedColumns;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=entry-locale-extension.service.d.ts.map
|
|
@@ -0,0 +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;AACnE,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;gBAPS,EAAE,EAAE,QAAQ,EAC9B,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,sBAAsB,EAKlC,SAAS,EAAE,mBAAmB;IAGnD,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;;;;;OAKG;YACW,iBAAiB;IAwB/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"}
|