@_linked/translation 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/lib/cjs/data/translation.json +8 -0
- package/lib/esm/archive.d.ts +133 -0
- package/lib/esm/archive.js +567 -0
- package/lib/esm/archive.js.map +1 -0
- package/lib/esm/authorization.d.ts +12 -0
- package/lib/esm/authorization.js +10 -0
- package/lib/esm/authorization.js.map +1 -0
- package/lib/esm/backend.d.ts +1 -0
- package/lib/esm/backend.js +3 -0
- package/lib/esm/backend.js.map +1 -0
- package/lib/esm/cdn-loader.d.ts +40 -0
- package/lib/esm/cdn-loader.js +200 -0
- package/lib/esm/cdn-loader.js.map +1 -0
- package/lib/esm/checks.d.ts +56 -0
- package/lib/esm/checks.js +288 -0
- package/lib/esm/checks.js.map +1 -0
- package/lib/esm/compile.d.ts +64 -0
- package/lib/esm/compile.js +99 -0
- package/lib/esm/compile.js.map +1 -0
- package/lib/esm/core/messages.d.ts +25 -0
- package/lib/esm/core/messages.js +76 -0
- package/lib/esm/core/messages.js.map +1 -0
- package/lib/esm/data/translation.json +8 -0
- package/lib/esm/discovery.d.ts +97 -0
- package/lib/esm/discovery.js +365 -0
- package/lib/esm/discovery.js.map +1 -0
- package/lib/esm/exchange.d.ts +100 -0
- package/lib/esm/exchange.js +223 -0
- package/lib/esm/exchange.js.map +1 -0
- package/lib/esm/export.d.ts +28 -0
- package/lib/esm/export.js +92 -0
- package/lib/esm/export.js.map +1 -0
- package/lib/esm/formats/csv.d.ts +8 -0
- package/lib/esm/formats/csv.js +397 -0
- package/lib/esm/formats/csv.js.map +1 -0
- package/lib/esm/formats/json.d.ts +30 -0
- package/lib/esm/formats/json.js +459 -0
- package/lib/esm/formats/json.js.map +1 -0
- package/lib/esm/formats/xliff.d.ts +12 -0
- package/lib/esm/formats/xliff.js +577 -0
- package/lib/esm/formats/xliff.js.map +1 -0
- package/lib/esm/import.d.ts +197 -0
- package/lib/esm/import.js +391 -0
- package/lib/esm/import.js.map +1 -0
- package/lib/esm/index.d.ts +42 -0
- package/lib/esm/index.js +34 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/key-sync-node.d.ts +29 -0
- package/lib/esm/key-sync-node.js +174 -0
- package/lib/esm/key-sync-node.js.map +1 -0
- package/lib/esm/key-sync.d.ts +46 -0
- package/lib/esm/key-sync.js +114 -0
- package/lib/esm/key-sync.js.map +1 -0
- package/lib/esm/key-version.d.ts +50 -0
- package/lib/esm/key-version.js +134 -0
- package/lib/esm/key-version.js.map +1 -0
- package/lib/esm/languages.d.ts +18 -0
- package/lib/esm/languages.js +74 -0
- package/lib/esm/languages.js.map +1 -0
- package/lib/esm/linked-discovery.d.ts +44 -0
- package/lib/esm/linked-discovery.js +267 -0
- package/lib/esm/linked-discovery.js.map +1 -0
- package/lib/esm/message-format.d.ts +41 -0
- package/lib/esm/message-format.js +133 -0
- package/lib/esm/message-format.js.map +1 -0
- package/lib/esm/mt.d.ts +111 -0
- package/lib/esm/mt.js +142 -0
- package/lib/esm/mt.js.map +1 -0
- package/lib/esm/ontologies/translation.d.ts +203 -0
- package/lib/esm/ontologies/translation.js +219 -0
- package/lib/esm/ontologies/translation.js.map +1 -0
- package/lib/esm/package.d.ts +4 -0
- package/lib/esm/package.js +11 -0
- package/lib/esm/package.js.map +1 -0
- package/lib/esm/react.d.ts +55 -0
- package/lib/esm/react.js +127 -0
- package/lib/esm/react.js.map +1 -0
- package/lib/esm/records.d.ts +151 -0
- package/lib/esm/records.js +2 -0
- package/lib/esm/records.js.map +1 -0
- package/lib/esm/release.d.ts +198 -0
- package/lib/esm/release.js +435 -0
- package/lib/esm/release.js.map +1 -0
- package/lib/esm/shape-catalog.d.ts +79 -0
- package/lib/esm/shape-catalog.js +118 -0
- package/lib/esm/shape-catalog.js.map +1 -0
- package/lib/esm/shapes/GlossaryTerm.d.ts +28 -0
- package/lib/esm/shapes/GlossaryTerm.js +94 -0
- package/lib/esm/shapes/GlossaryTerm.js.map +1 -0
- package/lib/esm/shapes/TranslationInventoryOrigin.d.ts +33 -0
- package/lib/esm/shapes/TranslationInventoryOrigin.js +209 -0
- package/lib/esm/shapes/TranslationInventoryOrigin.js.map +1 -0
- package/lib/esm/shapes/TranslationKey.d.ts +92 -0
- package/lib/esm/shapes/TranslationKey.js +194 -0
- package/lib/esm/shapes/TranslationKey.js.map +1 -0
- package/lib/esm/shapes/TranslationKeyVersion.d.ts +19 -0
- package/lib/esm/shapes/TranslationKeyVersion.js +125 -0
- package/lib/esm/shapes/TranslationKeyVersion.js.map +1 -0
- package/lib/esm/shapes/TranslationLanguage.d.ts +15 -0
- package/lib/esm/shapes/TranslationLanguage.js +102 -0
- package/lib/esm/shapes/TranslationLanguage.js.map +1 -0
- package/lib/esm/shapes/TranslationProvider.d.ts +409 -0
- package/lib/esm/shapes/TranslationProvider.js +1376 -0
- package/lib/esm/shapes/TranslationProvider.js.map +1 -0
- package/lib/esm/shapes/TranslationRelease.d.ts +36 -0
- package/lib/esm/shapes/TranslationRelease.js +149 -0
- package/lib/esm/shapes/TranslationRelease.js.map +1 -0
- package/lib/esm/shapes/TranslationRevision.d.ts +39 -0
- package/lib/esm/shapes/TranslationRevision.js +159 -0
- package/lib/esm/shapes/TranslationRevision.js.map +1 -0
- package/lib/esm/shapes/TranslationUnit.d.ts +26 -0
- package/lib/esm/shapes/TranslationUnit.js +111 -0
- package/lib/esm/shapes/TranslationUnit.js.map +1 -0
- package/package.json +134 -0
- package/src/archive.ts +894 -0
- package/src/authorization.ts +33 -0
- package/src/backend.ts +2 -0
- package/src/cdn-loader.ts +264 -0
- package/src/checks.ts +413 -0
- package/src/compile.ts +168 -0
- package/src/core/messages.ts +106 -0
- package/src/data/translation.json +8 -0
- package/src/discovery.ts +594 -0
- package/src/exchange.ts +428 -0
- package/src/export.ts +135 -0
- package/src/formats/csv.ts +459 -0
- package/src/formats/json.ts +587 -0
- package/src/formats/xliff.ts +708 -0
- package/src/import.ts +648 -0
- package/src/index.ts +207 -0
- package/src/key-sync-node.ts +269 -0
- package/src/key-sync.ts +168 -0
- package/src/key-version.ts +206 -0
- package/src/languages.ts +102 -0
- package/src/linked-discovery.ts +385 -0
- package/src/message-format.ts +214 -0
- package/src/mt.ts +248 -0
- package/src/ontologies/translation.ts +234 -0
- package/src/package.ts +19 -0
- package/src/react.tsx +250 -0
- package/src/records.ts +180 -0
- package/src/release.ts +726 -0
- package/src/shape-catalog.ts +198 -0
- package/src/shapes/GlossaryTerm.ts +61 -0
- package/src/shapes/TranslationInventoryOrigin.ts +131 -0
- package/src/shapes/TranslationKey.ts +174 -0
- package/src/shapes/TranslationKeyVersion.ts +77 -0
- package/src/shapes/TranslationLanguage.ts +51 -0
- package/src/shapes/TranslationProvider.ts +1755 -0
- package/src/shapes/TranslationRelease.ts +114 -0
- package/src/shapes/TranslationRevision.ts +105 -0
- package/src/shapes/TranslationUnit.ts +72 -0
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Isomorphic barrel (Plan 014 AD-G): side-effect imports register the ontology
|
|
2
|
+
// + shapes into the LINKED tree. Safe on client and server — no backend/Node
|
|
3
|
+
// code here (that lives in ./backend.ts). Ontology FIRST, then shapes.
|
|
4
|
+
import './ontologies/translation.js';
|
|
5
|
+
import './shapes/TranslationKey.js';
|
|
6
|
+
import './shapes/TranslationKeyVersion.js';
|
|
7
|
+
import './shapes/TranslationRelease.js';
|
|
8
|
+
import './shapes/TranslationUnit.js';
|
|
9
|
+
import './shapes/TranslationRevision.js';
|
|
10
|
+
import './shapes/GlossaryTerm.js';
|
|
11
|
+
import './shapes/TranslationInventoryOrigin.js';
|
|
12
|
+
export { canAuthorTranslation, configureTranslationAuthorization, } from './authorization.js';
|
|
13
|
+
export { TranslationKey } from './shapes/TranslationKey.js';
|
|
14
|
+
export { TranslationKeyVersion } from './shapes/TranslationKeyVersion.js';
|
|
15
|
+
export { TranslationRelease } from './shapes/TranslationRelease.js';
|
|
16
|
+
export { TranslationUnit } from './shapes/TranslationUnit.js';
|
|
17
|
+
export { TranslationRevision } from './shapes/TranslationRevision.js';
|
|
18
|
+
export { GlossaryTerm } from './shapes/GlossaryTerm.js';
|
|
19
|
+
export { TranslationInventoryOrigin } from './shapes/TranslationInventoryOrigin.js';
|
|
20
|
+
export { tr } from './ontologies/translation.js';
|
|
21
|
+
export { canonicalLanguageTag, classifyKeyVersion, createArgumentSignature, createMonotonicVersionId, deriveBackfillKeyVersion, deriveTranslationKeyVersionContract, sha256Hex, } from './key-version.js';
|
|
22
|
+
export { defineShapeTranslationCatalog, installShapeTranslationCatalog, shapeCatalogTranslationDiscoverySource, } from './shape-catalog.js';
|
|
23
|
+
export { discoverTranslationInventory, manifestTranslationDiscoverySource, parseTranslationDiscoveryManifest, translationBuildDeclarationsFromInventory, translationDeclarationIdentity, } from './discovery.js';
|
|
24
|
+
export { linkedPackageTranslationDiscoverySource, parseLinkedPackageTranslationCatalog, } from './linked-discovery.js';
|
|
25
|
+
export { clearTranslationFormatAdapters, getTranslationFormatAdapter, isConfirmedBuildDeclaration, listTranslationFormatAdapters, normalizeParsedTranslationExchangeDocument, registerTranslationFormatAdapter, validateTranslationExchangeDocument, } from './exchange.js';
|
|
26
|
+
export { commitVersionedImport, flattenMessages, hasIcuSyntax, planVersionedImport, runImport, tolgeeCdnSource, translationUnitContentHash, } from './import.js';
|
|
27
|
+
export { createTranslationExchangeDocument } from './export.js';
|
|
28
|
+
export { translationCsvAdapter } from './formats/csv.js';
|
|
29
|
+
export { xliff12Adapter, xliff20Adapter } from './formats/xliff.js';
|
|
30
|
+
export { DEFAULT_TRANSLATION_JSON_ARCHIVE_LIMITS, i18nextJsonFormatAdapter, parseI18nextJson, serializeI18nextJsonZip, } from './formats/json.js';
|
|
31
|
+
export { DEFAULT_TRANSLATION_ARCHIVE_LIMITS, createTranslationArchive, parseTranslationArchive, planTranslationArchiveRestore, TRANSLATION_ARCHIVE_COLLECTIONS, TRANSLATION_ARCHIVE_EXCLUSIONS, TRANSLATION_ARCHIVE_MANIFEST_PATH, } from './archive.js';
|
|
32
|
+
export { TranslationLanguage } from './shapes/TranslationLanguage.js';
|
|
33
|
+
export * from './languages.js';
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,uEAAuE;AACvE,OAAO,6BAA6B,CAAC;AACrC,OAAO,4BAA4B,CAAC;AACpC,OAAO,mCAAmC,CAAC;AAC3C,OAAO,gCAAgC,CAAC;AACxC,OAAO,6BAA6B,CAAC;AACrC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC;AAClC,OAAO,wCAAwC,CAAC;AAEhD,OAAO,EACL,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AACpF,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,mCAAmC,EACnC,SAAS,GACV,MAAM,kBAAkB,CAAC;AAQ1B,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,EAC9B,sCAAsC,GACvC,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,4BAA4B,EAC5B,kCAAkC,EAClC,iCAAiC,EACjC,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,gBAAgB,CAAC;AAexB,OAAO,EACL,uCAAuC,EACvC,oCAAoC,GACrC,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0CAA0C,EAC1C,gCAAgC,EAChC,mCAAmC,GACpC,MAAM,eAAe,CAAC;AAkBvB,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AAerB,OAAO,EAAE,iCAAiC,EAAE,MAAM,aAAa,CAAC;AAMhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EACL,uCAAuC,EACvC,wBAAwB,EACxB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAO3B,OAAO,EACL,kCAAkC,EAClC,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,EAC7B,+BAA+B,EAC/B,8BAA8B,EAC9B,iCAAiC,GAClC,MAAM,cAAc,CAAC;AAsCtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ExtractedTranslationKey, type TranslationKeySyncOptions, type TranslationKeySyncReport, type TranslationKeySyncTarget } from './key-sync.js';
|
|
2
|
+
import { type TranslationDiscoverySource } from './discovery.js';
|
|
3
|
+
/** Conventional code-canonical manifest for finite dynamic key families. */
|
|
4
|
+
export declare const TRANSLATION_DISCOVERY_MANIFEST_FILE = "translation.discovery.json";
|
|
5
|
+
/** package.json field pointing at a serialized LINKED translation catalog. */
|
|
6
|
+
export declare const LINKED_TRANSLATION_CATALOG_FIELD = "linkedTranslationCatalog";
|
|
7
|
+
/**
|
|
8
|
+
* Discover serialized app and dependency metadata from one exact workspace.
|
|
9
|
+
*
|
|
10
|
+
* This reads JSON only. Dependency JavaScript is never imported or evaluated.
|
|
11
|
+
* Dynamic keys live in `translation.discovery.json`; reusable LINKED packages
|
|
12
|
+
* opt in through package.json's `linkedTranslationCatalog` relative path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadWorkspaceTranslationDiscoverySources(root: string): Promise<TranslationDiscoverySource[]>;
|
|
15
|
+
export interface ScanTranslationSourceOptions {
|
|
16
|
+
extensions?: Iterable<string>;
|
|
17
|
+
ignoredDirectories?: Iterable<string>;
|
|
18
|
+
}
|
|
19
|
+
/** Recursively extract static translation keys from an application's source tree. */
|
|
20
|
+
export declare function scanTranslationSourceTree(root: string, options?: ScanTranslationSourceOptions): Promise<ExtractedTranslationKey[]>;
|
|
21
|
+
/** Portable discovery adapter for an ordinary application's source tree. */
|
|
22
|
+
export declare function sourceTreeTranslationDiscoverySource(root: string, options?: ScanTranslationSourceOptions): TranslationDiscoverySource;
|
|
23
|
+
export interface SyncTranslationSourceTreeOptions extends ScanTranslationSourceOptions, TranslationKeySyncOptions {
|
|
24
|
+
root: string;
|
|
25
|
+
appId: string;
|
|
26
|
+
target: TranslationKeySyncTarget;
|
|
27
|
+
}
|
|
28
|
+
/** Scan an app source tree and report or apply its keys through a provider adapter. */
|
|
29
|
+
export declare function syncTranslationSourceTree({ root, appId, target, dryRun, extensions, ignoredDirectories, }: SyncTranslationSourceTreeOptions): Promise<TranslationKeySyncReport>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { access, readdir, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
3
|
+
import { extractTranslationKeys, syncTranslationKeys, translationDeclarationsFromExtractedKeys, } from './key-sync.js';
|
|
4
|
+
import { manifestTranslationDiscoverySource, } from './discovery.js';
|
|
5
|
+
import { linkedPackageTranslationDiscoverySource } from './linked-discovery.js';
|
|
6
|
+
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']);
|
|
7
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'build',
|
|
10
|
+
'coverage',
|
|
11
|
+
'dist',
|
|
12
|
+
'lib',
|
|
13
|
+
'node_modules',
|
|
14
|
+
]);
|
|
15
|
+
/** Conventional code-canonical manifest for finite dynamic key families. */
|
|
16
|
+
export const TRANSLATION_DISCOVERY_MANIFEST_FILE = 'translation.discovery.json';
|
|
17
|
+
/** package.json field pointing at a serialized LINKED translation catalog. */
|
|
18
|
+
export const LINKED_TRANSLATION_CATALOG_FIELD = 'linkedTranslationCatalog';
|
|
19
|
+
async function readJsonFile(path) {
|
|
20
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
async function readOptionalJsonFile(path) {
|
|
23
|
+
try {
|
|
24
|
+
return await readJsonFile(path);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT')
|
|
28
|
+
return null;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function dependencyNames(manifest) {
|
|
33
|
+
const names = new Set();
|
|
34
|
+
for (const group of [manifest.dependencies, manifest.optionalDependencies]) {
|
|
35
|
+
if (!group || typeof group !== 'object' || Array.isArray(group))
|
|
36
|
+
continue;
|
|
37
|
+
for (const name of Object.keys(group))
|
|
38
|
+
names.add(name);
|
|
39
|
+
}
|
|
40
|
+
return [...names].sort();
|
|
41
|
+
}
|
|
42
|
+
async function dependencyDirectory(workspaceRoot, packageDirectory, dependency) {
|
|
43
|
+
let search = packageDirectory;
|
|
44
|
+
while (true) {
|
|
45
|
+
const candidate = join(search, 'node_modules', ...dependency.split('/'));
|
|
46
|
+
const boundary = relative(workspaceRoot, candidate);
|
|
47
|
+
if (!isAbsolute(boundary) && !boundary.startsWith('..')) {
|
|
48
|
+
try {
|
|
49
|
+
await access(join(candidate, 'package.json'));
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
catch (_a) {
|
|
53
|
+
// Hoisted dependencies may live in a parent node_modules directory.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (search === workspaceRoot)
|
|
57
|
+
return null;
|
|
58
|
+
const parent = dirname(search);
|
|
59
|
+
if (parent === search || relative(workspaceRoot, parent).startsWith('..')) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
search = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function safePackageFile(packageDirectory, file) {
|
|
66
|
+
const clean = file.trim();
|
|
67
|
+
if (!clean) {
|
|
68
|
+
throw new Error(`${LINKED_TRANSLATION_CATALOG_FIELD} must be a non-empty relative path.`);
|
|
69
|
+
}
|
|
70
|
+
const lexicalFile = resolve(packageDirectory, clean);
|
|
71
|
+
const lexicalBoundary = relative(packageDirectory, lexicalFile);
|
|
72
|
+
if (isAbsolute(lexicalBoundary) ||
|
|
73
|
+
lexicalBoundary === '..' ||
|
|
74
|
+
lexicalBoundary.startsWith(`..${sep}`)) {
|
|
75
|
+
throw new Error(`${LINKED_TRANSLATION_CATALOG_FIELD} must stay inside its package.`);
|
|
76
|
+
}
|
|
77
|
+
const [realPackageDirectory, realFile] = await Promise.all([
|
|
78
|
+
realpath(packageDirectory),
|
|
79
|
+
realpath(lexicalFile),
|
|
80
|
+
]);
|
|
81
|
+
const boundary = relative(realPackageDirectory, realFile);
|
|
82
|
+
if (isAbsolute(boundary) ||
|
|
83
|
+
boundary === '..' ||
|
|
84
|
+
boundary.startsWith(`..${sep}`)) {
|
|
85
|
+
throw new Error(`${LINKED_TRANSLATION_CATALOG_FIELD} must stay inside its package.`);
|
|
86
|
+
}
|
|
87
|
+
return realFile;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Discover serialized app and dependency metadata from one exact workspace.
|
|
91
|
+
*
|
|
92
|
+
* This reads JSON only. Dependency JavaScript is never imported or evaluated.
|
|
93
|
+
* Dynamic keys live in `translation.discovery.json`; reusable LINKED packages
|
|
94
|
+
* opt in through package.json's `linkedTranslationCatalog` relative path.
|
|
95
|
+
*/
|
|
96
|
+
export async function loadWorkspaceTranslationDiscoverySources(root) {
|
|
97
|
+
const workspaceRoot = resolve(root);
|
|
98
|
+
const sources = [];
|
|
99
|
+
const appManifest = await readOptionalJsonFile(join(workspaceRoot, TRANSLATION_DISCOVERY_MANIFEST_FILE));
|
|
100
|
+
if (appManifest) {
|
|
101
|
+
sources.push(manifestTranslationDiscoverySource(appManifest));
|
|
102
|
+
}
|
|
103
|
+
const visited = new Set();
|
|
104
|
+
const inspectPackage = async (packageDirectory) => {
|
|
105
|
+
const absoluteDirectory = resolve(packageDirectory);
|
|
106
|
+
if (visited.has(absoluteDirectory))
|
|
107
|
+
return;
|
|
108
|
+
const raw = await readOptionalJsonFile(join(absoluteDirectory, 'package.json'));
|
|
109
|
+
if (!raw)
|
|
110
|
+
return;
|
|
111
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
112
|
+
throw new Error(`Invalid package.json in ${absoluteDirectory}.`);
|
|
113
|
+
}
|
|
114
|
+
visited.add(absoluteDirectory);
|
|
115
|
+
const manifest = raw;
|
|
116
|
+
const catalogFile = manifest[LINKED_TRANSLATION_CATALOG_FIELD];
|
|
117
|
+
if (catalogFile !== undefined) {
|
|
118
|
+
if (typeof catalogFile !== 'string') {
|
|
119
|
+
throw new Error(`${LINKED_TRANSLATION_CATALOG_FIELD} in ${absoluteDirectory}/package.json must be a string.`);
|
|
120
|
+
}
|
|
121
|
+
sources.push(linkedPackageTranslationDiscoverySource(await readFile(await safePackageFile(absoluteDirectory, catalogFile))));
|
|
122
|
+
}
|
|
123
|
+
for (const dependency of dependencyNames(manifest)) {
|
|
124
|
+
const directory = await dependencyDirectory(workspaceRoot, absoluteDirectory, dependency);
|
|
125
|
+
if (directory)
|
|
126
|
+
await inspectPackage(directory);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
await inspectPackage(workspaceRoot);
|
|
130
|
+
return sources.sort((left, right) => left.name.localeCompare(right.name));
|
|
131
|
+
}
|
|
132
|
+
/** Recursively extract static translation keys from an application's source tree. */
|
|
133
|
+
export async function scanTranslationSourceTree(root, options = {}) {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
const absoluteRoot = resolve(root);
|
|
136
|
+
const extensions = new Set((_a = options.extensions) !== null && _a !== void 0 ? _a : SOURCE_EXTENSIONS);
|
|
137
|
+
const ignored = new Set((_b = options.ignoredDirectories) !== null && _b !== void 0 ? _b : IGNORED_DIRECTORIES);
|
|
138
|
+
const files = [];
|
|
139
|
+
const walk = async (directory) => {
|
|
140
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
141
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (entry.isSymbolicLink())
|
|
144
|
+
continue;
|
|
145
|
+
const path = resolve(directory, entry.name);
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
if (!ignored.has(entry.name))
|
|
148
|
+
await walk(path);
|
|
149
|
+
}
|
|
150
|
+
else if (entry.isFile() && extensions.has(extname(entry.name))) {
|
|
151
|
+
files.push(path);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
await walk(absoluteRoot);
|
|
156
|
+
const extracted = await Promise.all(files.map(async (file) => extractTranslationKeys(await readFile(file, 'utf8'), relative(absoluteRoot, file))));
|
|
157
|
+
return extracted.flat();
|
|
158
|
+
}
|
|
159
|
+
/** Portable discovery adapter for an ordinary application's source tree. */
|
|
160
|
+
export function sourceTreeTranslationDiscoverySource(root, options = {}) {
|
|
161
|
+
return {
|
|
162
|
+
name: `source-tree:${resolve(root)}`,
|
|
163
|
+
async *discover() {
|
|
164
|
+
const extracted = await scanTranslationSourceTree(root, options);
|
|
165
|
+
yield* translationDeclarationsFromExtractedKeys(extracted);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Scan an app source tree and report or apply its keys through a provider adapter. */
|
|
170
|
+
export async function syncTranslationSourceTree({ root, appId, target, dryRun, extensions, ignoredDirectories, }) {
|
|
171
|
+
const extracted = await scanTranslationSourceTree(root, { extensions, ignoredDirectories });
|
|
172
|
+
return syncTranslationKeys(target, appId, extracted, { dryRun });
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=key-sync-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-sync-node.js","sourceRoot":"","sources":["../../src/key-sync-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TranslationDeclaration, TranslationDiscoverySource } from './discovery.js';
|
|
2
|
+
export interface ExtractedTranslationKey {
|
|
3
|
+
key: string;
|
|
4
|
+
sourceText: string;
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TranslationKeySyncTarget {
|
|
9
|
+
list(data: {
|
|
10
|
+
appId: string;
|
|
11
|
+
}): Promise<Array<{
|
|
12
|
+
key: string;
|
|
13
|
+
sourceText?: string;
|
|
14
|
+
}>>;
|
|
15
|
+
upsert(data: {
|
|
16
|
+
appId: string;
|
|
17
|
+
key: string;
|
|
18
|
+
sourceText: string;
|
|
19
|
+
kind: 'ui';
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface TranslationKeySyncReport {
|
|
23
|
+
created: string[];
|
|
24
|
+
updated: string[];
|
|
25
|
+
unchanged: string[];
|
|
26
|
+
orphaned: string[];
|
|
27
|
+
conflicts: Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
defaults: string[];
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface TranslationKeySyncOptions {
|
|
33
|
+
/** Report changes without writing them to the target. */
|
|
34
|
+
dryRun?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Extract static `t(key, default)` and `<T keyName defaultValue>` call sites. */
|
|
37
|
+
export declare function extractTranslationKeys(source: string, file?: string): ExtractedTranslationKey[];
|
|
38
|
+
/** Adapt the shipped static extractor to the portable discovery contract. */
|
|
39
|
+
export declare function translationDeclarationsFromExtractedKeys(extracted: Iterable<ExtractedTranslationKey>): TranslationDeclaration[];
|
|
40
|
+
/** Create a portable source from already-extracted static call sites. */
|
|
41
|
+
export declare function staticTranslationDiscoverySource(extracted: Iterable<ExtractedTranslationKey>, name?: string): TranslationDiscoverySource;
|
|
42
|
+
/**
|
|
43
|
+
* Apply extracted keys. Source drift is delegated to the provider, which marks
|
|
44
|
+
* existing units stale. Orphans are deliberately report-only.
|
|
45
|
+
*/
|
|
46
|
+
export declare function syncTranslationKeys(target: TranslationKeySyncTarget, appId: string, extracted: ExtractedTranslationKey[], options?: TranslationKeySyncOptions): Promise<TranslationKeySyncReport>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
function literalText(node) {
|
|
3
|
+
return node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
4
|
+
? node.text
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
/** Extract static `t(key, default)` and `<T keyName defaultValue>` call sites. */
|
|
8
|
+
export function extractTranslationKeys(source, file = 'source.tsx') {
|
|
9
|
+
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, file.endsWith('x') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
10
|
+
const extracted = [];
|
|
11
|
+
const add = (keyNode, defaultNode) => {
|
|
12
|
+
const key = literalText(keyNode);
|
|
13
|
+
const sourceText = literalText(defaultNode);
|
|
14
|
+
if (!key || sourceText === undefined)
|
|
15
|
+
return;
|
|
16
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(keyNode.getStart(sourceFile));
|
|
17
|
+
extracted.push({ key, sourceText, file, line: line + 1 });
|
|
18
|
+
};
|
|
19
|
+
const visit = (node) => {
|
|
20
|
+
if (ts.isCallExpression(node) &&
|
|
21
|
+
ts.isIdentifier(node.expression) &&
|
|
22
|
+
node.expression.text === 't') {
|
|
23
|
+
add(node.arguments[0], node.arguments[1]);
|
|
24
|
+
}
|
|
25
|
+
else if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
|
|
26
|
+
if (node.tagName.getText(sourceFile) === 'T') {
|
|
27
|
+
const attrs = new Map(node.attributes.properties
|
|
28
|
+
.filter(ts.isJsxAttribute)
|
|
29
|
+
.map((attr) => [attr.name.getText(sourceFile), attr.initializer]));
|
|
30
|
+
const fromAttribute = (initializer) => {
|
|
31
|
+
if (!initializer)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (ts.isStringLiteral(initializer))
|
|
34
|
+
return initializer;
|
|
35
|
+
return ts.isJsxExpression(initializer) ? initializer.expression : undefined;
|
|
36
|
+
};
|
|
37
|
+
add(fromAttribute(attrs.get('keyName')), fromAttribute(attrs.get('defaultValue')));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
ts.forEachChild(node, visit);
|
|
41
|
+
};
|
|
42
|
+
visit(sourceFile);
|
|
43
|
+
return extracted;
|
|
44
|
+
}
|
|
45
|
+
/** Adapt the shipped static extractor to the portable discovery contract. */
|
|
46
|
+
export function translationDeclarationsFromExtractedKeys(extracted) {
|
|
47
|
+
return [...extracted].map((item) => ({
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
key: item.key,
|
|
50
|
+
sourceText: item.sourceText,
|
|
51
|
+
kind: 'ui',
|
|
52
|
+
format: 'simple',
|
|
53
|
+
provenance: {
|
|
54
|
+
source: 'code',
|
|
55
|
+
sourceId: item.file,
|
|
56
|
+
authoritative: true,
|
|
57
|
+
file: item.file,
|
|
58
|
+
line: item.line,
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
/** Create a portable source from already-extracted static call sites. */
|
|
63
|
+
export function staticTranslationDiscoverySource(extracted, name = 'static-code') {
|
|
64
|
+
const declarations = translationDeclarationsFromExtractedKeys(extracted);
|
|
65
|
+
return {
|
|
66
|
+
name,
|
|
67
|
+
async *discover() {
|
|
68
|
+
yield* declarations;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply extracted keys. Source drift is delegated to the provider, which marks
|
|
74
|
+
* existing units stale. Orphans are deliberately report-only.
|
|
75
|
+
*/
|
|
76
|
+
export async function syncTranslationKeys(target, appId, extracted, options = {}) {
|
|
77
|
+
var _a;
|
|
78
|
+
const existing = await target.list({ appId });
|
|
79
|
+
const existingByKey = new Map(existing.map((item) => [item.key, item]));
|
|
80
|
+
const defaultsByKey = new Map();
|
|
81
|
+
for (const item of extracted) {
|
|
82
|
+
const defaults = (_a = defaultsByKey.get(item.key)) !== null && _a !== void 0 ? _a : new Set();
|
|
83
|
+
defaults.add(item.sourceText);
|
|
84
|
+
defaultsByKey.set(item.key, defaults);
|
|
85
|
+
}
|
|
86
|
+
const report = {
|
|
87
|
+
created: [], updated: [], unchanged: [], orphaned: [], conflicts: [],
|
|
88
|
+
};
|
|
89
|
+
for (const [key, defaults] of defaultsByKey) {
|
|
90
|
+
if (defaults.size > 1) {
|
|
91
|
+
report.conflicts.push({ key, defaults: [...defaults].sort() });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const sourceText = [...defaults][0];
|
|
95
|
+
const current = existingByKey.get(key);
|
|
96
|
+
if (!current)
|
|
97
|
+
report.created.push(key);
|
|
98
|
+
else if (current.sourceText !== sourceText)
|
|
99
|
+
report.updated.push(key);
|
|
100
|
+
else {
|
|
101
|
+
report.unchanged.push(key);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!options.dryRun) {
|
|
105
|
+
await target.upsert({ appId, key, sourceText, kind: 'ui' });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
report.orphaned = existing
|
|
109
|
+
.map((item) => item.key)
|
|
110
|
+
.filter((key) => !defaultsByKey.has(key))
|
|
111
|
+
.sort();
|
|
112
|
+
return report;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=key-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-sync.js","sourceRoot":"","sources":["../../src/key-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MessageFormat } from './core/messages.js';
|
|
2
|
+
export interface TranslationKeyVersionContract {
|
|
3
|
+
sourceLanguage: string;
|
|
4
|
+
sourceText: string;
|
|
5
|
+
format: MessageFormat;
|
|
6
|
+
argumentSignature: string;
|
|
7
|
+
contractHash: string;
|
|
8
|
+
sourceHash: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DerivedBackfillKeyVersion extends TranslationKeyVersionContract {
|
|
11
|
+
versionId: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TranslationKeyVersionLike {
|
|
14
|
+
sourceText: string;
|
|
15
|
+
format: MessageFormat;
|
|
16
|
+
argumentSignature: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ExtractedTranslationKeyContract {
|
|
19
|
+
sourceText: string;
|
|
20
|
+
format: MessageFormat;
|
|
21
|
+
}
|
|
22
|
+
export interface KeyVersionDecision {
|
|
23
|
+
action: 'unchanged' | 'create-version' | 'retire';
|
|
24
|
+
reason: 'same-source-and-contract' | 'source-changed' | 'format-changed' | 'argument-contract-changed' | 'missing-from-source';
|
|
25
|
+
}
|
|
26
|
+
/** Monotonic ULID for newly authored key versions. */
|
|
27
|
+
export declare function createMonotonicVersionId(now?: number, randomBytes?: Uint8Array): string;
|
|
28
|
+
/** Portable SHA-256 used by both the browser-safe package and Node publishers. */
|
|
29
|
+
export declare function sha256Hex(value: string): Promise<string>;
|
|
30
|
+
export declare function canonicalLanguageTag(language: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Stable runtime-argument contract. Object/argument ordering is canonical so
|
|
33
|
+
* extraction order and translator word order cannot change the hash.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createArgumentSignature(sourceText: string, format: MessageFormat): string;
|
|
36
|
+
export declare function classifyKeyVersion(current: TranslationKeyVersionLike | undefined, extracted: ExtractedTranslationKeyContract | undefined): KeyVersionDecision;
|
|
37
|
+
export declare function deriveTranslationKeyVersionContract(input: {
|
|
38
|
+
sourceLanguage: string;
|
|
39
|
+
sourceText: string;
|
|
40
|
+
format: MessageFormat;
|
|
41
|
+
}): Promise<TranslationKeyVersionContract>;
|
|
42
|
+
/**
|
|
43
|
+
* Existing keys need a deterministic first version so a restarted migration
|
|
44
|
+
* cannot create a second node. Future authored versions use monotonic ULIDs.
|
|
45
|
+
*/
|
|
46
|
+
export declare function deriveBackfillKeyVersion(input: {
|
|
47
|
+
sourceLanguage: string;
|
|
48
|
+
sourceText: string;
|
|
49
|
+
format: MessageFormat;
|
|
50
|
+
}): Promise<DerivedBackfillKeyVersion>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { analyzeMessage } from './message-format.js';
|
|
2
|
+
const bytesToHex = (bytes) => [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
3
|
+
const ULID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
4
|
+
const BIGINT_FIVE = BigInt(5);
|
|
5
|
+
const BIGINT_EIGHT = BigInt(8);
|
|
6
|
+
const BIGINT_THIRTY_ONE = BigInt(31);
|
|
7
|
+
let lastUlidTime = -1;
|
|
8
|
+
let lastUlidRandom = new Uint8Array(10);
|
|
9
|
+
function encodeUlidTime(timestamp) {
|
|
10
|
+
let value = BigInt(timestamp);
|
|
11
|
+
let encoded = '';
|
|
12
|
+
for (let index = 0; index < 10; index++) {
|
|
13
|
+
encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;
|
|
14
|
+
value >>= BIGINT_FIVE;
|
|
15
|
+
}
|
|
16
|
+
return encoded;
|
|
17
|
+
}
|
|
18
|
+
function encodeUlidRandom(bytes) {
|
|
19
|
+
let value = BigInt(0);
|
|
20
|
+
for (const byte of bytes)
|
|
21
|
+
value = (value << BIGINT_EIGHT) | BigInt(byte);
|
|
22
|
+
let encoded = '';
|
|
23
|
+
for (let index = 0; index < 16; index++) {
|
|
24
|
+
encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;
|
|
25
|
+
value >>= BIGINT_FIVE;
|
|
26
|
+
}
|
|
27
|
+
return encoded;
|
|
28
|
+
}
|
|
29
|
+
function incrementRandom(bytes) {
|
|
30
|
+
const next = new Uint8Array(bytes);
|
|
31
|
+
for (let index = next.length - 1; index >= 0; index--) {
|
|
32
|
+
next[index] = (next[index] + 1) & 0xff;
|
|
33
|
+
if (next[index] !== 0)
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
throw new Error('ULID random component overflowed within one millisecond.');
|
|
37
|
+
}
|
|
38
|
+
/** Monotonic ULID for newly authored key versions. */
|
|
39
|
+
export function createMonotonicVersionId(now = Date.now(), randomBytes) {
|
|
40
|
+
if (!Number.isSafeInteger(now) || now < 0 || now > 0xffffffffffff) {
|
|
41
|
+
throw new Error('ULID timestamp is outside the supported 48-bit range.');
|
|
42
|
+
}
|
|
43
|
+
const timestamp = Math.max(now, lastUlidTime);
|
|
44
|
+
if (timestamp === lastUlidTime) {
|
|
45
|
+
lastUlidRandom = incrementRandom(lastUlidRandom);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const next = randomBytes !== null && randomBytes !== void 0 ? randomBytes : globalThis.crypto.getRandomValues(new Uint8Array(10));
|
|
49
|
+
if (next.length !== 10) {
|
|
50
|
+
throw new Error('ULID randomness must contain exactly 10 bytes.');
|
|
51
|
+
}
|
|
52
|
+
lastUlidTime = timestamp;
|
|
53
|
+
lastUlidRandom = new Uint8Array(next);
|
|
54
|
+
}
|
|
55
|
+
return `${encodeUlidTime(timestamp)}${encodeUlidRandom(lastUlidRandom)}`;
|
|
56
|
+
}
|
|
57
|
+
/** Portable SHA-256 used by both the browser-safe package and Node publishers. */
|
|
58
|
+
export async function sha256Hex(value) {
|
|
59
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
|
|
60
|
+
return bytesToHex(new Uint8Array(digest));
|
|
61
|
+
}
|
|
62
|
+
export function canonicalLanguageTag(language) {
|
|
63
|
+
var _a;
|
|
64
|
+
const value = language.trim();
|
|
65
|
+
if (!value)
|
|
66
|
+
throw new Error('sourceLanguage is required.');
|
|
67
|
+
try {
|
|
68
|
+
return (_a = Intl.getCanonicalLocales(value)[0]) !== null && _a !== void 0 ? _a : value;
|
|
69
|
+
}
|
|
70
|
+
catch (_b) {
|
|
71
|
+
throw new Error(`Invalid BCP-47 source language: ${language}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Stable runtime-argument contract. Object/argument ordering is canonical so
|
|
76
|
+
* extraction order and translator word order cannot change the hash.
|
|
77
|
+
*/
|
|
78
|
+
export function createArgumentSignature(sourceText, format) {
|
|
79
|
+
const analysis = analyzeMessage(sourceText, format);
|
|
80
|
+
if (!analysis.valid) {
|
|
81
|
+
throw new Error(`Cannot version invalid ${format.toUpperCase()} source text: ${analysis.error}`);
|
|
82
|
+
}
|
|
83
|
+
return JSON.stringify(analysis.arguments.map(({ name, kinds }) => [name, [...kinds].sort()]));
|
|
84
|
+
}
|
|
85
|
+
export function classifyKeyVersion(current, extracted) {
|
|
86
|
+
if (!extracted) {
|
|
87
|
+
return { action: 'retire', reason: 'missing-from-source' };
|
|
88
|
+
}
|
|
89
|
+
if (!current) {
|
|
90
|
+
return { action: 'create-version', reason: 'source-changed' };
|
|
91
|
+
}
|
|
92
|
+
if (current.format !== extracted.format) {
|
|
93
|
+
return { action: 'create-version', reason: 'format-changed' };
|
|
94
|
+
}
|
|
95
|
+
const nextSignature = createArgumentSignature(extracted.sourceText, extracted.format);
|
|
96
|
+
if (current.argumentSignature !== nextSignature) {
|
|
97
|
+
return {
|
|
98
|
+
action: 'create-version',
|
|
99
|
+
reason: 'argument-contract-changed',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (current.sourceText !== extracted.sourceText) {
|
|
103
|
+
return { action: 'create-version', reason: 'source-changed' };
|
|
104
|
+
}
|
|
105
|
+
return { action: 'unchanged', reason: 'same-source-and-contract' };
|
|
106
|
+
}
|
|
107
|
+
export async function deriveTranslationKeyVersionContract(input) {
|
|
108
|
+
const sourceLanguage = canonicalLanguageTag(input.sourceLanguage);
|
|
109
|
+
const argumentSignature = createArgumentSignature(input.sourceText, input.format);
|
|
110
|
+
const [sourceHash, contractHash] = await Promise.all([
|
|
111
|
+
sha256Hex(JSON.stringify([sourceLanguage, input.sourceText])),
|
|
112
|
+
sha256Hex(JSON.stringify([input.format, argumentSignature])),
|
|
113
|
+
]);
|
|
114
|
+
return {
|
|
115
|
+
sourceLanguage,
|
|
116
|
+
sourceText: input.sourceText,
|
|
117
|
+
format: input.format,
|
|
118
|
+
argumentSignature,
|
|
119
|
+
contractHash,
|
|
120
|
+
sourceHash,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Existing keys need a deterministic first version so a restarted migration
|
|
125
|
+
* cannot create a second node. Future authored versions use monotonic ULIDs.
|
|
126
|
+
*/
|
|
127
|
+
export async function deriveBackfillKeyVersion(input) {
|
|
128
|
+
const contract = await deriveTranslationKeyVersionContract(input);
|
|
129
|
+
return {
|
|
130
|
+
...contract,
|
|
131
|
+
versionId: `backfill-${contract.sourceHash.slice(0, 16)}-${contract.contractHash.slice(0, 16)}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=key-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-version.js","sourceRoot":"","sources":["../../src/key-version.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Portable language metadata. Codes are open BCP-47 strings, never an enum. */
|
|
2
|
+
export interface TranslationLanguageDefinition {
|
|
3
|
+
id: string;
|
|
4
|
+
code: string;
|
|
5
|
+
nativeName: string;
|
|
6
|
+
englishName: string;
|
|
7
|
+
direction: 'ltr' | 'rtl';
|
|
8
|
+
parent?: string;
|
|
9
|
+
fallback?: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
supported: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function canonicalLocale(code: string): string;
|
|
14
|
+
export declare function localeDirection(code: string): 'ltr' | 'rtl';
|
|
15
|
+
export declare function defineLanguage(code: string, input?: Partial<TranslationLanguageDefinition>): TranslationLanguageDefinition;
|
|
16
|
+
/** Ordered explicit fallback chain, ending at the source; safe against cycles. */
|
|
17
|
+
export declare function languageFallbacks(code: string, source: string, definitions?: readonly TranslationLanguageDefinition[]): string[];
|
|
18
|
+
export declare function validateLanguageDefinitions(definitions: readonly TranslationLanguageDefinition[]): void;
|