@_linked/translation 0.4.3 → 0.4.5

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.
@@ -5,6 +5,7 @@ const BIGINT_FIVE = BigInt(5);
5
5
  const BIGINT_EIGHT = BigInt(8);
6
6
  const BIGINT_THIRTY_ONE = BigInt(31);
7
7
  let lastUlidTime = -1;
8
+ // Always a private copy, so it is backed by a plain ArrayBuffer (never a SharedArrayBuffer).
8
9
  let lastUlidRandom = new Uint8Array(10);
9
10
  function encodeUlidTime(timestamp) {
10
11
  let value = BigInt(timestamp);
@@ -1 +1 @@
1
- {"version":3,"file":"key-version.js","sourceRoot":"","sources":["../../src/key-version.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageFormat } from './core/messages.js';\nimport { analyzeMessage } from './message-format.js';\n\nexport interface TranslationKeyVersionContract {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n argumentSignature: string;\n contractHash: string;\n sourceHash: string;\n}\n\nexport interface DerivedBackfillKeyVersion\n extends TranslationKeyVersionContract {\n versionId: string;\n}\n\nexport interface TranslationKeyVersionLike {\n sourceText: string;\n format: MessageFormat;\n argumentSignature: string;\n}\n\nexport interface ExtractedTranslationKeyContract {\n sourceText: string;\n format: MessageFormat;\n}\n\nexport interface KeyVersionDecision {\n action: 'unchanged' | 'create-version' | 'retire';\n reason:\n | 'same-source-and-contract'\n | 'source-changed'\n | 'format-changed'\n | 'argument-contract-changed'\n | 'missing-from-source';\n}\n\nconst bytesToHex = (bytes: Uint8Array): string =>\n [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n\nconst ULID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\nconst BIGINT_FIVE = BigInt(5);\nconst BIGINT_EIGHT = BigInt(8);\nconst BIGINT_THIRTY_ONE = BigInt(31);\nlet lastUlidTime = -1;\nlet lastUlidRandom = new Uint8Array(10);\n\nfunction encodeUlidTime(timestamp: number): string {\n let value = BigInt(timestamp);\n let encoded = '';\n for (let index = 0; index < 10; index++) {\n encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;\n value >>= BIGINT_FIVE;\n }\n return encoded;\n}\n\nfunction encodeUlidRandom(bytes: Uint8Array): string {\n let value = BigInt(0);\n for (const byte of bytes) value = (value << BIGINT_EIGHT) | BigInt(byte);\n let encoded = '';\n for (let index = 0; index < 16; index++) {\n encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;\n value >>= BIGINT_FIVE;\n }\n return encoded;\n}\n\nfunction incrementRandom(bytes: Uint8Array): Uint8Array {\n const next = new Uint8Array(bytes);\n for (let index = next.length - 1; index >= 0; index--) {\n next[index] = (next[index] + 1) & 0xff;\n if (next[index] !== 0) return next;\n }\n throw new Error('ULID random component overflowed within one millisecond.');\n}\n\n/** Monotonic ULID for newly authored key versions. */\nexport function createMonotonicVersionId(\n now = Date.now(),\n randomBytes?: Uint8Array,\n): string {\n if (!Number.isSafeInteger(now) || now < 0 || now > 0xffffffffffff) {\n throw new Error('ULID timestamp is outside the supported 48-bit range.');\n }\n const timestamp = Math.max(now, lastUlidTime);\n if (timestamp === lastUlidTime) {\n lastUlidRandom = incrementRandom(lastUlidRandom);\n } else {\n const next = randomBytes ?? globalThis.crypto.getRandomValues(new Uint8Array(10));\n if (next.length !== 10) {\n throw new Error('ULID randomness must contain exactly 10 bytes.');\n }\n lastUlidTime = timestamp;\n lastUlidRandom = new Uint8Array(next);\n }\n return `${encodeUlidTime(timestamp)}${encodeUlidRandom(lastUlidRandom)}`;\n}\n\n/** Portable SHA-256 used by both the browser-safe package and Node publishers. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(value),\n );\n return bytesToHex(new Uint8Array(digest));\n}\n\nexport function canonicalLanguageTag(language: string): string {\n const value = language.trim();\n if (!value) throw new Error('sourceLanguage is required.');\n try {\n return Intl.getCanonicalLocales(value)[0] ?? value;\n } catch {\n throw new Error(`Invalid BCP-47 source language: ${language}`);\n }\n}\n\n/**\n * Stable runtime-argument contract. Object/argument ordering is canonical so\n * extraction order and translator word order cannot change the hash.\n */\nexport function createArgumentSignature(\n sourceText: string,\n format: MessageFormat,\n): string {\n const analysis = analyzeMessage(sourceText, format);\n if (!analysis.valid) {\n throw new Error(\n `Cannot version invalid ${format.toUpperCase()} source text: ${analysis.error}`,\n );\n }\n return JSON.stringify(\n analysis.arguments.map(({ name, kinds }) => [name, [...kinds].sort()]),\n );\n}\n\nexport function classifyKeyVersion(\n current: TranslationKeyVersionLike | undefined,\n extracted: ExtractedTranslationKeyContract | undefined,\n): KeyVersionDecision {\n if (!extracted) {\n return { action: 'retire', reason: 'missing-from-source' };\n }\n if (!current) {\n return { action: 'create-version', reason: 'source-changed' };\n }\n if (current.format !== extracted.format) {\n return { action: 'create-version', reason: 'format-changed' };\n }\n const nextSignature = createArgumentSignature(\n extracted.sourceText,\n extracted.format,\n );\n if (current.argumentSignature !== nextSignature) {\n return {\n action: 'create-version',\n reason: 'argument-contract-changed',\n };\n }\n if (current.sourceText !== extracted.sourceText) {\n return { action: 'create-version', reason: 'source-changed' };\n }\n return { action: 'unchanged', reason: 'same-source-and-contract' };\n}\n\nexport async function deriveTranslationKeyVersionContract(input: {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n}): Promise<TranslationKeyVersionContract> {\n const sourceLanguage = canonicalLanguageTag(input.sourceLanguage);\n const argumentSignature = createArgumentSignature(\n input.sourceText,\n input.format,\n );\n const [sourceHash, contractHash] = await Promise.all([\n sha256Hex(JSON.stringify([sourceLanguage, input.sourceText])),\n sha256Hex(JSON.stringify([input.format, argumentSignature])),\n ]);\n return {\n sourceLanguage,\n sourceText: input.sourceText,\n format: input.format,\n argumentSignature,\n contractHash,\n sourceHash,\n };\n}\n\n/**\n * Existing keys need a deterministic first version so a restarted migration\n * cannot create a second node. Future authored versions use monotonic ULIDs.\n */\nexport async function deriveBackfillKeyVersion(input: {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n}): Promise<DerivedBackfillKeyVersion> {\n const contract = await deriveTranslationKeyVersionContract(input);\n return {\n ...contract,\n versionId: `backfill-${contract.sourceHash.slice(0, 16)}-${contract.contractHash.slice(0, 16)}`,\n };\n}\n"]}
1
+ {"version":3,"file":"key-version.js","sourceRoot":"","sources":["../../src/key-version.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageFormat } from './core/messages.js';\nimport { analyzeMessage } from './message-format.js';\n\nexport interface TranslationKeyVersionContract {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n argumentSignature: string;\n contractHash: string;\n sourceHash: string;\n}\n\nexport interface DerivedBackfillKeyVersion\n extends TranslationKeyVersionContract {\n versionId: string;\n}\n\nexport interface TranslationKeyVersionLike {\n sourceText: string;\n format: MessageFormat;\n argumentSignature: string;\n}\n\nexport interface ExtractedTranslationKeyContract {\n sourceText: string;\n format: MessageFormat;\n}\n\nexport interface KeyVersionDecision {\n action: 'unchanged' | 'create-version' | 'retire';\n reason:\n | 'same-source-and-contract'\n | 'source-changed'\n | 'format-changed'\n | 'argument-contract-changed'\n | 'missing-from-source';\n}\n\nconst bytesToHex = (bytes: Uint8Array): string =>\n [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n\nconst ULID_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\nconst BIGINT_FIVE = BigInt(5);\nconst BIGINT_EIGHT = BigInt(8);\nconst BIGINT_THIRTY_ONE = BigInt(31);\nlet lastUlidTime = -1;\n// Always a private copy, so it is backed by a plain ArrayBuffer (never a SharedArrayBuffer).\nlet lastUlidRandom: Uint8Array<ArrayBuffer> = new Uint8Array(10);\n\nfunction encodeUlidTime(timestamp: number): string {\n let value = BigInt(timestamp);\n let encoded = '';\n for (let index = 0; index < 10; index++) {\n encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;\n value >>= BIGINT_FIVE;\n }\n return encoded;\n}\n\nfunction encodeUlidRandom(bytes: Uint8Array): string {\n let value = BigInt(0);\n for (const byte of bytes) value = (value << BIGINT_EIGHT) | BigInt(byte);\n let encoded = '';\n for (let index = 0; index < 16; index++) {\n encoded = ULID_ALPHABET[Number(value & BIGINT_THIRTY_ONE)] + encoded;\n value >>= BIGINT_FIVE;\n }\n return encoded;\n}\n\nfunction incrementRandom(bytes: Uint8Array): Uint8Array<ArrayBuffer> {\n const next = new Uint8Array(bytes);\n for (let index = next.length - 1; index >= 0; index--) {\n next[index] = (next[index] + 1) & 0xff;\n if (next[index] !== 0) return next;\n }\n throw new Error('ULID random component overflowed within one millisecond.');\n}\n\n/** Monotonic ULID for newly authored key versions. */\nexport function createMonotonicVersionId(\n now = Date.now(),\n randomBytes?: Uint8Array,\n): string {\n if (!Number.isSafeInteger(now) || now < 0 || now > 0xffffffffffff) {\n throw new Error('ULID timestamp is outside the supported 48-bit range.');\n }\n const timestamp = Math.max(now, lastUlidTime);\n if (timestamp === lastUlidTime) {\n lastUlidRandom = incrementRandom(lastUlidRandom);\n } else {\n const next = randomBytes ?? globalThis.crypto.getRandomValues(new Uint8Array(10));\n if (next.length !== 10) {\n throw new Error('ULID randomness must contain exactly 10 bytes.');\n }\n lastUlidTime = timestamp;\n lastUlidRandom = new Uint8Array(next);\n }\n return `${encodeUlidTime(timestamp)}${encodeUlidRandom(lastUlidRandom)}`;\n}\n\n/** Portable SHA-256 used by both the browser-safe package and Node publishers. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(value),\n );\n return bytesToHex(new Uint8Array(digest));\n}\n\nexport function canonicalLanguageTag(language: string): string {\n const value = language.trim();\n if (!value) throw new Error('sourceLanguage is required.');\n try {\n return Intl.getCanonicalLocales(value)[0] ?? value;\n } catch {\n throw new Error(`Invalid BCP-47 source language: ${language}`);\n }\n}\n\n/**\n * Stable runtime-argument contract. Object/argument ordering is canonical so\n * extraction order and translator word order cannot change the hash.\n */\nexport function createArgumentSignature(\n sourceText: string,\n format: MessageFormat,\n): string {\n const analysis = analyzeMessage(sourceText, format);\n if (!analysis.valid) {\n throw new Error(\n `Cannot version invalid ${format.toUpperCase()} source text: ${analysis.error}`,\n );\n }\n return JSON.stringify(\n analysis.arguments.map(({ name, kinds }) => [name, [...kinds].sort()]),\n );\n}\n\nexport function classifyKeyVersion(\n current: TranslationKeyVersionLike | undefined,\n extracted: ExtractedTranslationKeyContract | undefined,\n): KeyVersionDecision {\n if (!extracted) {\n return { action: 'retire', reason: 'missing-from-source' };\n }\n if (!current) {\n return { action: 'create-version', reason: 'source-changed' };\n }\n if (current.format !== extracted.format) {\n return { action: 'create-version', reason: 'format-changed' };\n }\n const nextSignature = createArgumentSignature(\n extracted.sourceText,\n extracted.format,\n );\n if (current.argumentSignature !== nextSignature) {\n return {\n action: 'create-version',\n reason: 'argument-contract-changed',\n };\n }\n if (current.sourceText !== extracted.sourceText) {\n return { action: 'create-version', reason: 'source-changed' };\n }\n return { action: 'unchanged', reason: 'same-source-and-contract' };\n}\n\nexport async function deriveTranslationKeyVersionContract(input: {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n}): Promise<TranslationKeyVersionContract> {\n const sourceLanguage = canonicalLanguageTag(input.sourceLanguage);\n const argumentSignature = createArgumentSignature(\n input.sourceText,\n input.format,\n );\n const [sourceHash, contractHash] = await Promise.all([\n sha256Hex(JSON.stringify([sourceLanguage, input.sourceText])),\n sha256Hex(JSON.stringify([input.format, argumentSignature])),\n ]);\n return {\n sourceLanguage,\n sourceText: input.sourceText,\n format: input.format,\n argumentSignature,\n contractHash,\n sourceHash,\n };\n}\n\n/**\n * Existing keys need a deterministic first version so a restarted migration\n * cannot create a second node. Future authored versions use monotonic ULIDs.\n */\nexport async function deriveBackfillKeyVersion(input: {\n sourceLanguage: string;\n sourceText: string;\n format: MessageFormat;\n}): Promise<DerivedBackfillKeyVersion> {\n const contract = await deriveTranslationKeyVersionContract(input);\n return {\n ...contract,\n versionId: `backfill-${contract.sourceHash.slice(0, 16)}-${contract.contractHash.slice(0, 16)}`,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@_linked/translation",
3
3
  "description": "LINKED translation management — graph-native i18n keys + units, Tolgee-compatible client, CDN delivery. Built inside Create Now for now; structured to extract to its own repo unchanged.",
4
- "version": "0.4.3",
4
+ "version": "0.4.5",
5
5
  "packageManager": "npm@11.20.0",
6
6
  "linkedPackage": true,
7
7
  "type": "module",
@@ -42,53 +42,43 @@
42
42
  "exports": {
43
43
  ".": {
44
44
  "types": "./lib/esm/index.d.ts",
45
- "development": "./src/index.ts",
46
45
  "import": "./lib/esm/index.js"
47
46
  },
48
47
  "./react": {
49
48
  "types": "./lib/esm/react.d.ts",
50
- "development": "./src/react.tsx",
51
49
  "import": "./lib/esm/react.js"
52
50
  },
53
51
  "./key-sync": {
54
52
  "types": "./lib/esm/key-sync.d.ts",
55
- "development": "./src/key-sync.ts",
56
53
  "import": "./lib/esm/key-sync.js"
57
54
  },
58
55
  "./key-sync/node": {
59
56
  "types": "./lib/esm/key-sync-node.d.ts",
60
- "development": "./src/key-sync-node.ts",
61
57
  "import": "./lib/esm/key-sync-node.js"
62
58
  },
63
59
  "./shape-catalog": {
64
60
  "types": "./lib/esm/shape-catalog.d.ts",
65
- "development": "./src/shape-catalog.ts",
66
61
  "import": "./lib/esm/shape-catalog.js"
67
62
  },
68
63
  "./discovery": {
69
64
  "types": "./lib/esm/discovery.d.ts",
70
- "development": "./src/discovery.ts",
71
65
  "import": "./lib/esm/discovery.js"
72
66
  },
73
67
  "./linked-discovery": {
74
68
  "types": "./lib/esm/linked-discovery.d.ts",
75
- "development": "./src/linked-discovery.ts",
76
69
  "import": "./lib/esm/linked-discovery.js"
77
70
  },
78
71
  "./exchange": {
79
72
  "types": "./lib/esm/exchange.d.ts",
80
- "development": "./src/exchange.ts",
81
73
  "import": "./lib/esm/exchange.js"
82
74
  },
83
75
  "./package.json": "./package.json",
84
76
  "./*.js": {
85
77
  "types": "./lib/esm/*.d.ts",
86
- "development": "./src/*.ts",
87
78
  "import": "./lib/esm/*.js"
88
79
  },
89
80
  "./*": {
90
81
  "types": "./lib/esm/*.d.ts",
91
- "development": "./src/*.ts",
92
82
  "import": "./lib/esm/*.js"
93
83
  }
94
84
  },
@@ -102,9 +92,9 @@
102
92
  "dependencies": {
103
93
  "@_linked/core": "^2.22.8",
104
94
  "@_linked/server-utils": "^1.1.0",
105
- "@formatjs/icu-messageformat-parser": "3.5.15",
95
+ "@formatjs/icu-messageformat-parser": "3.5.20",
106
96
  "fflate": "^0.8.2",
107
- "intl-messageformat": "11.2.12"
97
+ "intl-messageformat": "12.1.2"
108
98
  },
109
99
  "peerDependencies": {
110
100
  "react": "*",
@@ -122,13 +112,13 @@
122
112
  "@_linked/cli": "^1.11.1",
123
113
  "@testing-library/dom": "^10.4.1",
124
114
  "@testing-library/react": "^16.3.0",
125
- "@types/node": "^22.13.14",
115
+ "@types/node": "^24.0.0",
126
116
  "@types/react": "^18.3.24",
127
117
  "@types/react-dom": "^18.3.7",
128
118
  "jsdom": "^26.1.0",
129
119
  "react": "^18.3.1",
130
120
  "react-dom": "^18.3.1",
131
- "typescript": "5.7.3",
121
+ "typescript": "5.9.3",
132
122
  "vitest": "^3.2.4",
133
123
  "@changesets/cli": "^3.0.0",
134
124
  "@changesets/changelog-github": "^1.0.0"
@@ -44,7 +44,8 @@ const BIGINT_FIVE = BigInt(5);
44
44
  const BIGINT_EIGHT = BigInt(8);
45
45
  const BIGINT_THIRTY_ONE = BigInt(31);
46
46
  let lastUlidTime = -1;
47
- let lastUlidRandom = new Uint8Array(10);
47
+ // Always a private copy, so it is backed by a plain ArrayBuffer (never a SharedArrayBuffer).
48
+ let lastUlidRandom: Uint8Array<ArrayBuffer> = new Uint8Array(10);
48
49
 
49
50
  function encodeUlidTime(timestamp: number): string {
50
51
  let value = BigInt(timestamp);
@@ -67,7 +68,7 @@ function encodeUlidRandom(bytes: Uint8Array): string {
67
68
  return encoded;
68
69
  }
69
70
 
70
- function incrementRandom(bytes: Uint8Array): Uint8Array {
71
+ function incrementRandom(bytes: Uint8Array): Uint8Array<ArrayBuffer> {
71
72
  const next = new Uint8Array(bytes);
72
73
  for (let index = next.length - 1; index >= 0; index--) {
73
74
  next[index] = (next[index] + 1) & 0xff;
@@ -1,8 +0,0 @@
1
- {
2
- "@context": {
3
- "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
4
- "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
5
- "tr": "https://id.linked.cm/translation/vocab#"
6
- },
7
- "@graph": []
8
- }