@vouchington/localization 0.0.0 → 0.0.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 +9 -2
- package/dist/bounds.d.mts +2 -2
- package/dist/bounds.mjs +2 -2
- package/dist/catalog.d.mts +4 -0
- package/dist/catalog.mjs +33 -1
- package/dist/descriptors.d.mts +1 -0
- package/dist/descriptors.mjs +9 -0
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +5 -2
- package/dist/shard-edit.d.mts +2 -0
- package/dist/shard-edit.mjs +20 -0
- package/dist/shard-merge.d.mts +6 -0
- package/dist/shard-merge.mjs +147 -0
- package/dist/shard-text.d.mts +3 -0
- package/dist/shard-text.mjs +48 -0
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -5,5 +5,12 @@ normalization (`en` aliases `en-US`), exact and terminal-prefix selector validat
|
|
|
5
5
|
fallback, consumer membership, and deterministic catalog serialization. It does not load catalogs,
|
|
6
6
|
open SQLite, or interpolate message text.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Catalog shards on disk are a JSON array with **one compact message object per line**. `id` is the
|
|
9
|
+
first key so git and line editors can add, remove, or update a message without parsing the file.
|
|
10
|
+
`parseCatalogShardText` rejects pretty-printed JSON, `{ messages }` wrappers, and unsorted ids.
|
|
11
|
+
|
|
12
|
+
`@vouchington/localization-compiler` compiles those shards into an immutable SQLite artifact,
|
|
13
|
+
resolves the same selectors locally, and ships `upsert` / `remove` / `git-merge` for the line
|
|
14
|
+
format. `git-merge` is 3-way: union independent ids, then merge consumers, descriptor, and
|
|
15
|
+
each locale. Same-locale edits conflict and write `<<<<<<< ours` markers; adding `es` on one
|
|
16
|
+
side and `fr` on the other does not.
|
package/dist/bounds.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const DEFAULT_LOCALIZATION_BOUNDS: {
|
|
2
2
|
readonly maxLocales: 8;
|
|
3
|
-
readonly maxSelectors:
|
|
4
|
-
readonly maxMessages:
|
|
3
|
+
readonly maxSelectors: 64;
|
|
4
|
+
readonly maxMessages: 3000;
|
|
5
5
|
readonly maxBytes: number;
|
|
6
6
|
};
|
|
7
7
|
export declare class LocalizationBoundError extends RangeError {
|
package/dist/bounds.mjs
CHANGED
package/dist/catalog.d.mts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { CatalogMessage } from './types.mts';
|
|
2
2
|
export declare function serializeCatalogMessages(messages: readonly CatalogMessage[]): string;
|
|
3
|
+
export declare function serializeCatalogLine(message: CatalogMessage): string;
|
|
4
|
+
export declare function serializeCatalogShard(messages: readonly CatalogMessage[]): string;
|
|
5
|
+
export declare function serializeCatalogShardFromLines(lines: readonly string[]): string;
|
|
6
|
+
export declare function catalogLineId(line: string): string;
|
|
3
7
|
export declare function catalogMessageFromRecord(value: unknown): CatalogMessage;
|
package/dist/catalog.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { uniqueConsumers } from './consumers.mjs';
|
|
2
|
+
import { compareCodePoints } from './compare.mjs';
|
|
2
3
|
import { parseDescriptor } from './descriptors.mjs';
|
|
3
4
|
import { canonicalJson } from './serialize.mjs';
|
|
4
5
|
import { isMessageId } from './selectors.mjs';
|
|
6
|
+
const ID_PREFIX = '{"id":"';
|
|
5
7
|
export function serializeCatalogMessages(messages) {
|
|
6
8
|
return canonicalJson([...messages]
|
|
7
|
-
.toSorted((left, right) => (left.id
|
|
9
|
+
.toSorted((left, right) => compareCodePoints(left.id, right.id))
|
|
8
10
|
.map((message) => ({
|
|
9
11
|
consumers: uniqueConsumers(message.consumers),
|
|
10
12
|
descriptor: message.descriptor,
|
|
@@ -12,6 +14,36 @@ export function serializeCatalogMessages(messages) {
|
|
|
12
14
|
translations: message.translations,
|
|
13
15
|
})));
|
|
14
16
|
}
|
|
17
|
+
export function serializeCatalogLine(message) {
|
|
18
|
+
const normalized = catalogMessageFromRecord(message);
|
|
19
|
+
// id first for line edits; not canonicalJson key order (revision hashing uses that).
|
|
20
|
+
return `{"id":${canonicalJson(normalized.id)},"consumers":${canonicalJson(normalized.consumers)},"descriptor":${canonicalJson(normalized.descriptor)},"translations":${canonicalJson(normalized.translations)}}`;
|
|
21
|
+
}
|
|
22
|
+
export function serializeCatalogShard(messages) {
|
|
23
|
+
return serializeCatalogShardFromLines([...messages]
|
|
24
|
+
.toSorted((left, right) => compareCodePoints(left.id, right.id))
|
|
25
|
+
.map(serializeCatalogLine));
|
|
26
|
+
}
|
|
27
|
+
export function serializeCatalogShardFromLines(lines) {
|
|
28
|
+
if (lines.length === 0)
|
|
29
|
+
return '[]\n';
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
if (line.includes('\n') || line.includes('\r')) {
|
|
32
|
+
throw new TypeError('Catalog line must not contain newlines');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return `[\n${lines.map((line, index) => (index < lines.length - 1 ? `${line},` : line)).join('\n')}\n]\n`;
|
|
36
|
+
}
|
|
37
|
+
export function catalogLineId(line) {
|
|
38
|
+
const body = line.endsWith(',') ? line.slice(0, -1) : line;
|
|
39
|
+
if (!body.startsWith(ID_PREFIX)) {
|
|
40
|
+
throw new TypeError('Catalog line must start with {"id":');
|
|
41
|
+
}
|
|
42
|
+
const end = body.indexOf('"', ID_PREFIX.length);
|
|
43
|
+
if (end === -1)
|
|
44
|
+
throw new TypeError('Catalog line is missing a message id');
|
|
45
|
+
return body.slice(ID_PREFIX.length, end);
|
|
46
|
+
}
|
|
15
47
|
export function catalogMessageFromRecord(value) {
|
|
16
48
|
if (!isPlainObject(value) || typeof value.id !== 'string' || !isMessageId(value.id)) {
|
|
17
49
|
throw new TypeError('Catalog message is missing a valid id');
|
package/dist/descriptors.d.mts
CHANGED
|
@@ -2,5 +2,6 @@ import { type MessageDescriptor, type PluralForms, type TranslationValue } from
|
|
|
2
2
|
export declare function isPluralForms(value: unknown): value is PluralForms;
|
|
3
3
|
export declare function isSelectPluralCases(value: unknown): value is Readonly<Record<string, PluralForms>>;
|
|
4
4
|
export declare function isTranslationValue(value: unknown): value is TranslationValue;
|
|
5
|
+
export declare function translationMatchesDescriptor(descriptor: MessageDescriptor | null, value: unknown): boolean;
|
|
5
6
|
export declare function descriptorSignature(descriptor: MessageDescriptor): string;
|
|
6
7
|
export declare function parseDescriptor(value: unknown): MessageDescriptor | null;
|
package/dist/descriptors.mjs
CHANGED
|
@@ -14,6 +14,15 @@ export function isSelectPluralCases(value) {
|
|
|
14
14
|
export function isTranslationValue(value) {
|
|
15
15
|
return typeof value === 'string' || isPluralForms(value) || isSelectPluralCases(value);
|
|
16
16
|
}
|
|
17
|
+
export function translationMatchesDescriptor(descriptor, value) {
|
|
18
|
+
if (descriptor === null)
|
|
19
|
+
return typeof value === 'string';
|
|
20
|
+
if (descriptor.kind === 'plural')
|
|
21
|
+
return isPluralForms(value);
|
|
22
|
+
return (isSelectPluralCases(value) &&
|
|
23
|
+
descriptorSignature({ ...descriptor, cases: Object.keys(value) }) ===
|
|
24
|
+
descriptorSignature(descriptor));
|
|
25
|
+
}
|
|
17
26
|
export function descriptorSignature(descriptor) {
|
|
18
27
|
return JSON.stringify({
|
|
19
28
|
kind: descriptor.kind,
|
package/dist/index.d.mts
CHANGED
|
@@ -7,8 +7,11 @@ export { dedupeSelectors, isMessageId, parseSelector, prefixRange, selectorMatch
|
|
|
7
7
|
export { assertMessageCount, assertPayloadBytes, normalizeLocalizationRequest } from './request.mts';
|
|
8
8
|
export { firstAvailableTranslation, leafForTranslation, selectedIds } from './selection.mts';
|
|
9
9
|
export { assertSamePlaceholders, placeholdersIn, uniquePlaceholders } from './placeholders.mts';
|
|
10
|
-
export { descriptorSignature, isPluralForms, isSelectPluralCases, isTranslationValue, parseDescriptor, } from './descriptors.mts';
|
|
10
|
+
export { descriptorSignature, isPluralForms, isSelectPluralCases, isTranslationValue, parseDescriptor, translationMatchesDescriptor, } from './descriptors.mts';
|
|
11
11
|
export { canonicalJson } from './serialize.mts';
|
|
12
12
|
export { compareCodePoints } from './compare.mts';
|
|
13
|
-
export { catalogMessageFromRecord, serializeCatalogMessages } from './catalog.mts';
|
|
13
|
+
export { catalogLineId, catalogMessageFromRecord, serializeCatalogLine, serializeCatalogMessages, serializeCatalogShard, serializeCatalogShardFromLines, } from './catalog.mts';
|
|
14
|
+
export { catalogShardLines, parseCatalogShardText } from './shard-text.mts';
|
|
15
|
+
export { removeCatalogLine, upsertCatalogLine } from './shard-edit.mts';
|
|
16
|
+
export { CatalogMergeConflict, mergeCatalogShards } from './shard-merge.mts';
|
|
14
17
|
export { createLocalizationBatch, etagMatches, localizationEtag, serializeLocalizationBatch, } from './wire.mts';
|
package/dist/index.mjs
CHANGED
|
@@ -6,8 +6,11 @@ export { dedupeSelectors, isMessageId, parseSelector, prefixRange, selectorMatch
|
|
|
6
6
|
export { assertMessageCount, assertPayloadBytes, normalizeLocalizationRequest } from './request.mjs';
|
|
7
7
|
export { firstAvailableTranslation, leafForTranslation, selectedIds } from './selection.mjs';
|
|
8
8
|
export { assertSamePlaceholders, placeholdersIn, uniquePlaceholders } from './placeholders.mjs';
|
|
9
|
-
export { descriptorSignature, isPluralForms, isSelectPluralCases, isTranslationValue, parseDescriptor, } from './descriptors.mjs';
|
|
9
|
+
export { descriptorSignature, isPluralForms, isSelectPluralCases, isTranslationValue, parseDescriptor, translationMatchesDescriptor, } from './descriptors.mjs';
|
|
10
10
|
export { canonicalJson } from './serialize.mjs';
|
|
11
11
|
export { compareCodePoints } from './compare.mjs';
|
|
12
|
-
export { catalogMessageFromRecord, serializeCatalogMessages } from './catalog.mjs';
|
|
12
|
+
export { catalogLineId, catalogMessageFromRecord, serializeCatalogLine, serializeCatalogMessages, serializeCatalogShard, serializeCatalogShardFromLines, } from './catalog.mjs';
|
|
13
|
+
export { catalogShardLines, parseCatalogShardText } from './shard-text.mjs';
|
|
14
|
+
export { removeCatalogLine, upsertCatalogLine } from './shard-edit.mjs';
|
|
15
|
+
export { CatalogMergeConflict, mergeCatalogShards } from './shard-merge.mjs';
|
|
13
16
|
export { createLocalizationBatch, etagMatches, localizationEtag, serializeLocalizationBatch, } from './wire.mjs';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { catalogLineId, catalogMessageFromRecord, serializeCatalogLine, serializeCatalogShardFromLines, } from './catalog.mjs';
|
|
2
|
+
import { compareCodePoints } from './compare.mjs';
|
|
3
|
+
import { catalogShardLines } from './shard-text.mjs';
|
|
4
|
+
export function upsertCatalogLine(shardText, messageJson) {
|
|
5
|
+
const line = serializeCatalogLine(catalogMessageFromRecord(JSON.parse(messageJson)));
|
|
6
|
+
const id = catalogLineId(line);
|
|
7
|
+
const next = catalogShardLines(shardText).filter((current) => catalogLineId(current) !== id);
|
|
8
|
+
next.push(line);
|
|
9
|
+
return serializeCatalogShardFromLines(sortedLines(next));
|
|
10
|
+
}
|
|
11
|
+
export function removeCatalogLine(shardText, id) {
|
|
12
|
+
const lines = catalogShardLines(shardText);
|
|
13
|
+
const next = lines.filter((line) => catalogLineId(line) !== id);
|
|
14
|
+
if (next.length === lines.length)
|
|
15
|
+
throw new TypeError(`Catalog does not contain "${id}"`);
|
|
16
|
+
return serializeCatalogShardFromLines(sortedLines(next));
|
|
17
|
+
}
|
|
18
|
+
function sortedLines(lines) {
|
|
19
|
+
return [...lines].toSorted((left, right) => compareCodePoints(catalogLineId(left), catalogLineId(right)));
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class CatalogMergeConflict extends Error {
|
|
2
|
+
readonly ids: readonly string[];
|
|
3
|
+
readonly text: string;
|
|
4
|
+
constructor(ids: readonly string[], text: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function mergeCatalogShards(ancestor: string, ours: string, theirs: string): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { catalogLineId, catalogMessageFromRecord, serializeCatalogLine, serializeCatalogShardFromLines, } from './catalog.mjs';
|
|
2
|
+
import { compareCodePoints } from './compare.mjs';
|
|
3
|
+
import { uniqueConsumers } from './consumers.mjs';
|
|
4
|
+
import { canonicalJson } from './serialize.mjs';
|
|
5
|
+
import { catalogShardLines } from './shard-text.mjs';
|
|
6
|
+
import { translationMatchesDescriptor } from './descriptors.mjs';
|
|
7
|
+
import { LOCALIZATION_CONSUMERS } from './types.mjs';
|
|
8
|
+
const CONFLICT = Symbol('conflict');
|
|
9
|
+
export class CatalogMergeConflict extends Error {
|
|
10
|
+
ids;
|
|
11
|
+
text;
|
|
12
|
+
constructor(ids, text) {
|
|
13
|
+
super(`Catalog merge conflict for ${ids.map((id) => `"${id}"`).join(', ')}`);
|
|
14
|
+
this.name = 'CatalogMergeConflict';
|
|
15
|
+
this.ids = ids;
|
|
16
|
+
this.text = text;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function mergeCatalogShards(ancestor, ours, theirs) {
|
|
20
|
+
const base = lineMap(ancestor);
|
|
21
|
+
const left = lineMap(ours);
|
|
22
|
+
const right = lineMap(theirs);
|
|
23
|
+
const ids = [...new Set([...base.keys(), ...left.keys(), ...right.keys()])].toSorted(compareCodePoints);
|
|
24
|
+
const lines = [];
|
|
25
|
+
const entries = [];
|
|
26
|
+
const conflicts = [];
|
|
27
|
+
for (const id of ids) {
|
|
28
|
+
const oursLine = left.get(id);
|
|
29
|
+
const theirsLine = right.get(id);
|
|
30
|
+
const kept = mergeLine(base.get(id), oursLine, theirsLine);
|
|
31
|
+
if (kept === false) {
|
|
32
|
+
conflicts.push(id);
|
|
33
|
+
entries.push({ ours: oursLine, theirs: theirsLine });
|
|
34
|
+
}
|
|
35
|
+
else if (kept !== undefined) {
|
|
36
|
+
lines.push(kept);
|
|
37
|
+
entries.push(kept);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (conflicts.length > 0)
|
|
41
|
+
throw new CatalogMergeConflict(conflicts, serializeConflicted(entries));
|
|
42
|
+
return serializeCatalogShardFromLines(lines);
|
|
43
|
+
}
|
|
44
|
+
function lineMap(text) {
|
|
45
|
+
const map = new Map();
|
|
46
|
+
for (const line of catalogShardLines(text)) {
|
|
47
|
+
const id = catalogLineId(line);
|
|
48
|
+
if (map.has(id))
|
|
49
|
+
throw new TypeError(`Duplicate message id "${id}"`);
|
|
50
|
+
map.set(id, line);
|
|
51
|
+
}
|
|
52
|
+
return map;
|
|
53
|
+
}
|
|
54
|
+
function serializeConflicted(entries) {
|
|
55
|
+
const chunks = ['['];
|
|
56
|
+
for (const [index, entry] of entries.entries()) {
|
|
57
|
+
const suffix = index < entries.length - 1 ? ',' : '';
|
|
58
|
+
if (typeof entry === 'string') {
|
|
59
|
+
chunks.push(`${entry}${suffix}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
chunks.push('<<<<<<< ours');
|
|
63
|
+
if (entry.ours !== undefined)
|
|
64
|
+
chunks.push(`${entry.ours}${suffix}`);
|
|
65
|
+
chunks.push('=======');
|
|
66
|
+
if (entry.theirs !== undefined)
|
|
67
|
+
chunks.push(`${entry.theirs}${suffix}`);
|
|
68
|
+
chunks.push('>>>>>>> theirs');
|
|
69
|
+
}
|
|
70
|
+
chunks.push(']');
|
|
71
|
+
return `${chunks.join('\n')}\n`;
|
|
72
|
+
}
|
|
73
|
+
function mergeLine(ancestor, ours, theirs) {
|
|
74
|
+
if (ours === theirs)
|
|
75
|
+
return ours;
|
|
76
|
+
if (ours === ancestor)
|
|
77
|
+
return theirs;
|
|
78
|
+
if (theirs === ancestor)
|
|
79
|
+
return ours;
|
|
80
|
+
if (ours === undefined || theirs === undefined)
|
|
81
|
+
return false;
|
|
82
|
+
return mergeEditedLines(ancestor, ours, theirs);
|
|
83
|
+
}
|
|
84
|
+
function mergeEditedLines(ancestor, ours, theirs) {
|
|
85
|
+
const base = ancestor === undefined ? emptyDraft(ours) : messageFromLine(ancestor);
|
|
86
|
+
const left = messageFromLine(ours);
|
|
87
|
+
const right = messageFromLine(theirs);
|
|
88
|
+
const consumers = mergeConsumers(base.consumers, left.consumers, right.consumers);
|
|
89
|
+
const descriptor = mergeValue(base.descriptor, left.descriptor, right.descriptor);
|
|
90
|
+
const translations = mergeTranslations(base.translations, left.translations, right.translations);
|
|
91
|
+
if (descriptor === CONFLICT || translations === CONFLICT)
|
|
92
|
+
return false;
|
|
93
|
+
if (!Object.values(translations).every((value) => translationMatchesDescriptor(descriptor, value))) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return serializeCatalogLine(catalogMessageFromRecord({ id: left.id, consumers, descriptor, translations }));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function emptyDraft(line) {
|
|
104
|
+
return { id: catalogLineId(line), descriptor: null, consumers: [], translations: {} };
|
|
105
|
+
}
|
|
106
|
+
function messageFromLine(line) {
|
|
107
|
+
return catalogMessageFromRecord(JSON.parse(line));
|
|
108
|
+
}
|
|
109
|
+
function mergeConsumers(ancestor, ours, theirs) {
|
|
110
|
+
const kept = [];
|
|
111
|
+
for (const consumer of LOCALIZATION_CONSUMERS) {
|
|
112
|
+
if (mergeValue(ancestor.includes(consumer), ours.includes(consumer), theirs.includes(consumer)) === true) {
|
|
113
|
+
kept.push(consumer);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return uniqueConsumers(kept);
|
|
117
|
+
}
|
|
118
|
+
function mergeTranslations(ancestor, ours, theirs) {
|
|
119
|
+
const locales = new Set([...Object.keys(ancestor), ...Object.keys(ours), ...Object.keys(theirs)]);
|
|
120
|
+
const translations = {};
|
|
121
|
+
for (const locale of [...locales].toSorted(compareCodePoints)) {
|
|
122
|
+
const value = mergeValue(ancestor[locale], ours[locale], theirs[locale]);
|
|
123
|
+
if (value === CONFLICT)
|
|
124
|
+
return CONFLICT;
|
|
125
|
+
if (value !== undefined)
|
|
126
|
+
translations[locale] = value;
|
|
127
|
+
}
|
|
128
|
+
return translations;
|
|
129
|
+
}
|
|
130
|
+
function mergeValue(ancestor, ours, theirs) {
|
|
131
|
+
if (same(ours, theirs))
|
|
132
|
+
return ours;
|
|
133
|
+
if (same(ours, ancestor))
|
|
134
|
+
return theirs;
|
|
135
|
+
if (same(theirs, ancestor))
|
|
136
|
+
return ours;
|
|
137
|
+
return CONFLICT;
|
|
138
|
+
}
|
|
139
|
+
function same(left, right) {
|
|
140
|
+
if (left === right)
|
|
141
|
+
return true;
|
|
142
|
+
if (left === undefined || right === undefined || left === null || right === null)
|
|
143
|
+
return false;
|
|
144
|
+
if (typeof left !== 'object' || typeof right !== 'object')
|
|
145
|
+
return false;
|
|
146
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
147
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { catalogLineId, catalogMessageFromRecord, serializeCatalogLine } from './catalog.mjs';
|
|
2
|
+
import { compareCodePoints } from './compare.mjs';
|
|
3
|
+
export function catalogShardLines(text) {
|
|
4
|
+
if (text === '[]\n' || text === '')
|
|
5
|
+
return [];
|
|
6
|
+
if (!text.startsWith('[\n') || !text.endsWith('\n]\n')) {
|
|
7
|
+
throw new TypeError(text.includes('\r')
|
|
8
|
+
? 'Catalog shard must use LF line endings'
|
|
9
|
+
: 'Catalog shard must be a JSON array with one message per line');
|
|
10
|
+
}
|
|
11
|
+
const raw = text.slice(2, -3).split('\n');
|
|
12
|
+
if (raw.length === 1 && raw[0] === '') {
|
|
13
|
+
throw new TypeError('Empty catalog shard must be written []\n');
|
|
14
|
+
}
|
|
15
|
+
return raw.map((line, index) => {
|
|
16
|
+
const needsComma = index < raw.length - 1;
|
|
17
|
+
if (line.endsWith(',') !== needsComma) {
|
|
18
|
+
throw new TypeError('Catalog shard commas must appear on every line except the last');
|
|
19
|
+
}
|
|
20
|
+
const body = needsComma ? line.slice(0, -1) : line;
|
|
21
|
+
if (body.length === 0) {
|
|
22
|
+
throw new TypeError('Catalog shard messages must be exactly one line each');
|
|
23
|
+
}
|
|
24
|
+
catalogLineId(body);
|
|
25
|
+
return body;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function parseCatalogShardText(text) {
|
|
29
|
+
const lines = catalogShardLines(text);
|
|
30
|
+
const messages = lines.map((line) => {
|
|
31
|
+
const message = catalogMessageFromRecord(JSON.parse(line));
|
|
32
|
+
const canonical = serializeCatalogLine(message);
|
|
33
|
+
if (canonical !== line) {
|
|
34
|
+
throw new TypeError(`Catalog line for "${message.id}" is not canonical; expected ${canonical}. Run format to rewrite.`);
|
|
35
|
+
}
|
|
36
|
+
return message;
|
|
37
|
+
});
|
|
38
|
+
for (let index = 1; index < messages.length; index++) {
|
|
39
|
+
const previous = messages[index - 1].id;
|
|
40
|
+
const current = messages[index].id;
|
|
41
|
+
if (previous === current)
|
|
42
|
+
throw new TypeError(`Duplicate message id "${current}"`);
|
|
43
|
+
if (compareCodePoints(previous, current) > 0) {
|
|
44
|
+
throw new TypeError('Catalog shard ids must be sorted');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return messages;
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vouchington/localization",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Browser-safe localization catalog contracts, locale fallback, and selector validation.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-platform/tree/main/packages/localization#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -31,11 +31,10 @@
|
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsc --project tsconfig.build.json",
|
|
36
|
-
"prepack": "pnpm run build"
|
|
37
|
-
},
|
|
38
34
|
"engines": {
|
|
39
35
|
"node": ">=24.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc --project tsconfig.build.json"
|
|
40
39
|
}
|
|
41
|
-
}
|
|
40
|
+
}
|