scribe-cms 0.0.12 → 0.0.14
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 +21 -6
- package/dist/cli/index.cjs +1207 -204
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1208 -205
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prompt-translate.d.ts +3 -0
- package/dist/cli/prompt-translate.d.ts.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +969 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +970 -168
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +136 -62
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +136 -62
- package/dist/runtime.js.map +1 -1
- package/dist/src/config/resolve-config.d.ts.map +1 -1
- package/dist/src/core/introspect-schema.d.ts +7 -1
- package/dist/src/core/introspect-schema.d.ts.map +1 -1
- package/dist/src/core/types.d.ts +6 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/create-project.d.ts.map +1 -1
- package/dist/src/i18n/resolve-document.d.ts +1 -1
- package/dist/src/i18n/resolve-document.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/storage/batch-jobs.d.ts +53 -0
- package/dist/src/storage/batch-jobs.d.ts.map +1 -0
- package/dist/src/storage/sqlite.d.ts.map +1 -1
- package/dist/src/translate/batch-worklist.d.ts +87 -0
- package/dist/src/translate/batch-worklist.d.ts.map +1 -0
- package/dist/src/translate/gemini-batch.d.ts +26 -0
- package/dist/src/translate/gemini-batch.d.ts.map +1 -0
- package/dist/src/translate/gemini-client.d.ts +18 -0
- package/dist/src/translate/gemini-client.d.ts.map +1 -1
- package/dist/src/translate/gemini-models.d.ts +9 -0
- package/dist/src/translate/gemini-models.d.ts.map +1 -1
- package/dist/src/translate/gemini-pricing.d.ts +2 -1
- package/dist/src/translate/gemini-pricing.d.ts.map +1 -1
- package/dist/src/translate/page-translator.d.ts +25 -52
- package/dist/src/translate/page-translator.d.ts.map +1 -1
- package/dist/src/translate/prompts/translation-prompt.d.ts +6 -0
- package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -1
- package/dist/src/translate/response-schema.d.ts +8 -1
- package/dist/src/translate/response-schema.d.ts.map +1 -1
- package/dist/src/translate/retry.d.ts +16 -0
- package/dist/src/translate/retry.d.ts.map +1 -0
- package/dist/src/translate/sanitize-mdx-jsx.d.ts.map +1 -1
- package/dist/src/translate/translate-core.d.ts +155 -0
- package/dist/src/translate/translate-core.d.ts.map +1 -0
- package/dist/studio/server.cjs +84 -47
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.js +84 -47
- package/dist/studio/server.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime.cjs
CHANGED
|
@@ -138,6 +138,7 @@ function resolveConfig(input, baseDir) {
|
|
|
138
138
|
if (localeRouting.strategy === "search-param" && !localeRouting.param) {
|
|
139
139
|
throw new Error('scribe config: localeRouting search-param requires a "param" name');
|
|
140
140
|
}
|
|
141
|
+
const localeFallbacks = raw.localeFallbacks === false ? {} : deriveLocaleFallbacks(raw.locales, defaultLocale);
|
|
141
142
|
const projectRoot = path__default.default.resolve(process.cwd(), raw.rootDir);
|
|
142
143
|
const contentRoot = path__default.default.resolve(projectRoot, raw.contentDir ?? "content");
|
|
143
144
|
const storePath = path__default.default.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
@@ -167,6 +168,7 @@ function resolveConfig(input, baseDir) {
|
|
|
167
168
|
defaultLocale,
|
|
168
169
|
localeRouting,
|
|
169
170
|
localePresets: raw.localePresets,
|
|
171
|
+
localeFallbacks,
|
|
170
172
|
translate: raw.translate,
|
|
171
173
|
types
|
|
172
174
|
};
|
|
@@ -176,6 +178,25 @@ function resolveConfig(input, baseDir) {
|
|
|
176
178
|
});
|
|
177
179
|
return config;
|
|
178
180
|
}
|
|
181
|
+
function deriveLocaleFallbacks(locales, defaultLocale) {
|
|
182
|
+
const byLowercase = new Map(locales.map((l) => [l.toLowerCase(), l]));
|
|
183
|
+
const fallbacks = {};
|
|
184
|
+
for (const locale of locales) {
|
|
185
|
+
const subtags = locale.split("-");
|
|
186
|
+
if (subtags.length < 2) continue;
|
|
187
|
+
const chain = [];
|
|
188
|
+
for (let end = subtags.length - 1; end >= 1; end--) {
|
|
189
|
+
const prefix = byLowercase.get(subtags.slice(0, end).join("-").toLowerCase());
|
|
190
|
+
if (prefix && prefix !== locale && prefix !== defaultLocale) {
|
|
191
|
+
chain.push(prefix);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (chain.length > 0) {
|
|
195
|
+
fallbacks[locale] = chain;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return fallbacks;
|
|
199
|
+
}
|
|
179
200
|
var FIELD_KIND = /* @__PURE__ */ Symbol.for("@genlook/scribe/fieldKind");
|
|
180
201
|
var RELATION_META = /* @__PURE__ */ Symbol.for("@genlook/scribe/relationMeta");
|
|
181
202
|
function getFieldKind(schema) {
|
|
@@ -205,20 +226,16 @@ function getRelationTarget(schema) {
|
|
|
205
226
|
}
|
|
206
227
|
return null;
|
|
207
228
|
}
|
|
208
|
-
function
|
|
229
|
+
function peelOptionalWrappers(schema) {
|
|
209
230
|
let current = schema;
|
|
210
231
|
for (let i = 0; i < 8; i++) {
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
current =
|
|
232
|
+
const type = current._def?.type;
|
|
233
|
+
if (type === "optional" || type === "nullable") {
|
|
234
|
+
current = current.unwrap();
|
|
214
235
|
continue;
|
|
215
236
|
}
|
|
216
|
-
if (
|
|
217
|
-
current =
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (anySchema._def?.innerType) {
|
|
221
|
-
current = anySchema._def.innerType;
|
|
237
|
+
if (type === "default") {
|
|
238
|
+
current = current.removeDefault();
|
|
222
239
|
continue;
|
|
223
240
|
}
|
|
224
241
|
break;
|
|
@@ -227,6 +244,12 @@ function unwrapSchema(schema) {
|
|
|
227
244
|
}
|
|
228
245
|
|
|
229
246
|
// src/core/introspect-schema.ts
|
|
247
|
+
function getArrayElement(schema) {
|
|
248
|
+
if (schema instanceof Object && "element" in schema && schema._def?.type === "array") {
|
|
249
|
+
return schema.element;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
230
253
|
function introspectSchema(schema, prefix = []) {
|
|
231
254
|
const relation = getRelationTarget(schema);
|
|
232
255
|
if (relation && prefix.length > 0) {
|
|
@@ -240,7 +263,10 @@ function introspectSchema(schema, prefix = []) {
|
|
|
240
263
|
}
|
|
241
264
|
];
|
|
242
265
|
}
|
|
243
|
-
|
|
266
|
+
if (prefix.length > 0 && getFieldKind(schema) === "translatable") {
|
|
267
|
+
return [{ path: prefix, kind: "translatable" }];
|
|
268
|
+
}
|
|
269
|
+
const base = peelOptionalWrappers(schema);
|
|
244
270
|
if (base instanceof Object && "shape" in base) {
|
|
245
271
|
const shape = base.shape;
|
|
246
272
|
const fields = [];
|
|
@@ -249,9 +275,9 @@ function introspectSchema(schema, prefix = []) {
|
|
|
249
275
|
}
|
|
250
276
|
return fields;
|
|
251
277
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const elementBase =
|
|
278
|
+
const element = getArrayElement(base);
|
|
279
|
+
if (element) {
|
|
280
|
+
const elementBase = peelOptionalWrappers(element);
|
|
255
281
|
if (elementBase instanceof Object && "shape" in elementBase) {
|
|
256
282
|
const shape = elementBase.shape;
|
|
257
283
|
const fields = [];
|
|
@@ -263,15 +289,48 @@ function introspectSchema(schema, prefix = []) {
|
|
|
263
289
|
}
|
|
264
290
|
return [{ path: prefix, kind: getFieldKind(schema) }];
|
|
265
291
|
}
|
|
266
|
-
function
|
|
267
|
-
const
|
|
292
|
+
function buildPathTrie(paths) {
|
|
293
|
+
const root = { leaf: false, children: /* @__PURE__ */ new Map() };
|
|
268
294
|
for (const path6 of paths) {
|
|
269
|
-
|
|
295
|
+
let node = root;
|
|
296
|
+
for (const segment of path6) {
|
|
297
|
+
let child = node.children.get(segment);
|
|
298
|
+
if (!child) {
|
|
299
|
+
child = { leaf: false, children: /* @__PURE__ */ new Map() };
|
|
300
|
+
node.children.set(segment, child);
|
|
301
|
+
}
|
|
302
|
+
node = child;
|
|
303
|
+
}
|
|
304
|
+
node.leaf = true;
|
|
305
|
+
}
|
|
306
|
+
return root;
|
|
307
|
+
}
|
|
308
|
+
function extractNode(data, trie) {
|
|
309
|
+
if (trie.leaf) return data;
|
|
310
|
+
const star = trie.children.get("*");
|
|
311
|
+
if (star) {
|
|
312
|
+
if (!Array.isArray(data)) return void 0;
|
|
313
|
+
return data.map(
|
|
314
|
+
(item) => typeof item === "object" && item !== null ? extractNode(item, star) ?? {} : {}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return void 0;
|
|
318
|
+
const record = data;
|
|
319
|
+
const out = {};
|
|
320
|
+
let any = false;
|
|
321
|
+
for (const [key, child] of trie.children) {
|
|
322
|
+
const value = extractNode(record[key], child);
|
|
270
323
|
if (value !== void 0) {
|
|
271
|
-
|
|
324
|
+
out[key] = value;
|
|
325
|
+
any = true;
|
|
272
326
|
}
|
|
273
327
|
}
|
|
274
|
-
return out;
|
|
328
|
+
return any ? out : void 0;
|
|
329
|
+
}
|
|
330
|
+
function extractByPaths(data, paths) {
|
|
331
|
+
if (paths.length === 0) return {};
|
|
332
|
+
const extracted = extractNode(data, buildPathTrie(paths));
|
|
333
|
+
return extracted && typeof extracted === "object" && !Array.isArray(extracted) ? extracted : {};
|
|
275
334
|
}
|
|
276
335
|
function pickTranslatable(data, schema) {
|
|
277
336
|
const translatablePaths = introspectSchema(schema).filter((f) => f.kind === "translatable").map((f) => f.path);
|
|
@@ -286,32 +345,6 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
|
286
345
|
const translatable = pickTranslatable(localeData, schema);
|
|
287
346
|
return deepMerge(structural, translatable);
|
|
288
347
|
}
|
|
289
|
-
function getAtPath(obj, path6) {
|
|
290
|
-
let current = obj;
|
|
291
|
-
for (const segment of path6) {
|
|
292
|
-
if (segment === "*") {
|
|
293
|
-
if (!Array.isArray(current)) return void 0;
|
|
294
|
-
return current.map(
|
|
295
|
-
(item) => typeof item === "object" && item !== null ? getAtPath(item, path6.slice(path6.indexOf("*") + 1)) : void 0
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
299
|
-
current = current[segment];
|
|
300
|
-
}
|
|
301
|
-
return current;
|
|
302
|
-
}
|
|
303
|
-
function setAtPath(obj, path6, value) {
|
|
304
|
-
if (path6.length === 0) return;
|
|
305
|
-
if (path6.length === 1) {
|
|
306
|
-
obj[path6[0]] = value;
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const [head, ...rest] = path6;
|
|
310
|
-
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
311
|
-
obj[head] = {};
|
|
312
|
-
}
|
|
313
|
-
setAtPath(obj[head], rest, value);
|
|
314
|
-
}
|
|
315
348
|
function deepMerge(base, overlay) {
|
|
316
349
|
const out = { ...base };
|
|
317
350
|
for (const [key, value] of Object.entries(overlay)) {
|
|
@@ -462,7 +495,7 @@ function seoFieldsFromEn(enDoc) {
|
|
|
462
495
|
canonicalPathOverride: enDoc.canonicalPathOverride
|
|
463
496
|
};
|
|
464
497
|
}
|
|
465
|
-
var SCHEMA_VERSION =
|
|
498
|
+
var SCHEMA_VERSION = 5;
|
|
466
499
|
var MIGRATIONS = [
|
|
467
500
|
`CREATE TABLE IF NOT EXISTS meta (
|
|
468
501
|
key TEXT PRIMARY KEY,
|
|
@@ -510,7 +543,30 @@ var MIGRATIONS = [
|
|
|
510
543
|
UNIQUE (content_type, en_slug, en_hash)
|
|
511
544
|
)`,
|
|
512
545
|
`CREATE INDEX IF NOT EXISTS idx_en_snapshots_lookup
|
|
513
|
-
ON en_snapshots(content_type, en_slug, created_at DESC)
|
|
546
|
+
ON en_snapshots(content_type, en_slug, created_at DESC)`,
|
|
547
|
+
`CREATE TABLE IF NOT EXISTS translation_batch_jobs (
|
|
548
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
549
|
+
job_name TEXT NOT NULL UNIQUE,
|
|
550
|
+
model TEXT NOT NULL,
|
|
551
|
+
display_model TEXT NOT NULL,
|
|
552
|
+
created_at TEXT NOT NULL,
|
|
553
|
+
state TEXT NOT NULL,
|
|
554
|
+
completed_at TEXT
|
|
555
|
+
)`,
|
|
556
|
+
`CREATE TABLE IF NOT EXISTS translation_batch_items (
|
|
557
|
+
job_id INTEGER NOT NULL,
|
|
558
|
+
request_index INTEGER NOT NULL,
|
|
559
|
+
content_type TEXT NOT NULL,
|
|
560
|
+
en_slug TEXT NOT NULL,
|
|
561
|
+
locale TEXT NOT NULL,
|
|
562
|
+
en_hash TEXT NOT NULL,
|
|
563
|
+
snapshot_id INTEGER NOT NULL,
|
|
564
|
+
status TEXT NOT NULL,
|
|
565
|
+
error TEXT,
|
|
566
|
+
PRIMARY KEY (job_id, request_index)
|
|
567
|
+
)`,
|
|
568
|
+
`CREATE INDEX IF NOT EXISTS idx_batch_items_status
|
|
569
|
+
ON translation_batch_items(status)`
|
|
514
570
|
];
|
|
515
571
|
function resolveStorePath(config) {
|
|
516
572
|
return config.storePath;
|
|
@@ -533,6 +589,8 @@ function addColumnIfMissing(db, table, column, ddlType) {
|
|
|
533
589
|
}
|
|
534
590
|
}
|
|
535
591
|
function readSchemaVersion(db) {
|
|
592
|
+
const metaTable = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'meta'`).get();
|
|
593
|
+
if (!metaTable) return 0;
|
|
536
594
|
const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
|
|
537
595
|
return row ? Number.parseInt(row.value, 10) || 0 : 0;
|
|
538
596
|
}
|
|
@@ -593,7 +651,8 @@ function sanitizeMdxJsxAttributeQuotes(body) {
|
|
|
593
651
|
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
594
652
|
const attrStart = i;
|
|
595
653
|
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
596
|
-
body.slice(attrStart, i);
|
|
654
|
+
const attrName = body.slice(attrStart, i);
|
|
655
|
+
if (!attrName) break;
|
|
597
656
|
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
598
657
|
if (body[i] !== "=") {
|
|
599
658
|
tagOut += body.slice(attrStart, i);
|
|
@@ -602,10 +661,12 @@ function sanitizeMdxJsxAttributeQuotes(body) {
|
|
|
602
661
|
tagOut += body.slice(attrStart, i);
|
|
603
662
|
tagOut += "=";
|
|
604
663
|
i += 1;
|
|
664
|
+
const wsStart = i;
|
|
605
665
|
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
666
|
+
tagOut += body.slice(wsStart, i);
|
|
606
667
|
const quote = body[i];
|
|
607
668
|
if (quote !== '"' && quote !== "'") {
|
|
608
|
-
|
|
669
|
+
break;
|
|
609
670
|
}
|
|
610
671
|
if (quote === "'") {
|
|
611
672
|
const valStart2 = i + 1;
|
|
@@ -888,7 +949,7 @@ function createContentLoader(config, type) {
|
|
|
888
949
|
}
|
|
889
950
|
|
|
890
951
|
// src/i18n/resolve-document.ts
|
|
891
|
-
function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, localeRouting) {
|
|
952
|
+
function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, localeRouting, fallbackLocales = []) {
|
|
892
953
|
const urlBuilder = createUrlBuilder({
|
|
893
954
|
locales: [defaultLocale, locale],
|
|
894
955
|
defaultLocale,
|
|
@@ -902,15 +963,28 @@ function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, lo
|
|
|
902
963
|
for (const [docLocale, docIdx] of allDocs) {
|
|
903
964
|
const found = docIdx.bySlug.get(slug);
|
|
904
965
|
if (!found) continue;
|
|
905
|
-
const
|
|
906
|
-
|
|
966
|
+
const enSlug = docLocale === defaultLocale ? found.slug : found.enSlug;
|
|
967
|
+
let target;
|
|
968
|
+
let targetLocale = locale;
|
|
969
|
+
for (const candidateLocale of [locale, ...fallbackLocales]) {
|
|
970
|
+
const cand = candidateLocale === defaultLocale ? allDocs.get(candidateLocale)?.bySlug.get(enSlug) : allDocs.get(candidateLocale)?.byEnSlug.get(enSlug);
|
|
971
|
+
if (cand) {
|
|
972
|
+
target = cand;
|
|
973
|
+
targetLocale = candidateLocale;
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (target) {
|
|
978
|
+
if (target.slug === slug) {
|
|
979
|
+
return { document: target, actualLocale: targetLocale };
|
|
980
|
+
}
|
|
907
981
|
if (!type.path) {
|
|
908
982
|
return { document: null, actualLocale: locale };
|
|
909
983
|
}
|
|
910
984
|
return {
|
|
911
985
|
document: null,
|
|
912
986
|
actualLocale: locale,
|
|
913
|
-
shouldRedirectTo: urlBuilder.resolvePath(type.path,
|
|
987
|
+
shouldRedirectTo: urlBuilder.resolvePath(type.path, target.slug, locale)
|
|
914
988
|
};
|
|
915
989
|
}
|
|
916
990
|
if (docLocale === defaultLocale) {
|
|
@@ -941,13 +1015,6 @@ function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, lo
|
|
|
941
1015
|
}
|
|
942
1016
|
return { document: null, actualLocale: locale };
|
|
943
1017
|
}
|
|
944
|
-
function getSlugForLocale(document, sourceLocale, targetLocale, allDocs, defaultLocale) {
|
|
945
|
-
if (sourceLocale === targetLocale) return document.slug;
|
|
946
|
-
const englishSlug = sourceLocale === defaultLocale ? document.slug : document.enSlug;
|
|
947
|
-
if (!englishSlug) return null;
|
|
948
|
-
if (targetLocale === defaultLocale) return englishSlug;
|
|
949
|
-
return allDocs.get(targetLocale)?.byEnSlug.get(englishSlug)?.slug ?? null;
|
|
950
|
-
}
|
|
951
1018
|
|
|
952
1019
|
// src/create-project.ts
|
|
953
1020
|
function comparatorFor(orderBy) {
|
|
@@ -1000,7 +1067,8 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1000
1067
|
config.defaultLocale,
|
|
1001
1068
|
load(),
|
|
1002
1069
|
type,
|
|
1003
|
-
config.localeRouting
|
|
1070
|
+
config.localeRouting,
|
|
1071
|
+
config.localeFallbacks[locale] ?? []
|
|
1004
1072
|
);
|
|
1005
1073
|
if (result.document && type.path) {
|
|
1006
1074
|
return {
|
|
@@ -1022,8 +1090,14 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1022
1090
|
const params = [];
|
|
1023
1091
|
for (const locale of options.locales ?? config.locales) {
|
|
1024
1092
|
const localeIdx = all.get(locale);
|
|
1093
|
+
const fallbacks = config.localeFallbacks[locale] ?? [];
|
|
1025
1094
|
for (const doc of enIdx.bySlug.values()) {
|
|
1026
|
-
|
|
1095
|
+
let slug;
|
|
1096
|
+
if (locale === config.defaultLocale) {
|
|
1097
|
+
slug = doc.slug;
|
|
1098
|
+
} else {
|
|
1099
|
+
slug = localeIdx?.byEnSlug.get(doc.slug)?.slug ?? fallbacks.map((fb) => all.get(fb)?.byEnSlug.get(doc.slug)?.slug).find((s) => s !== void 0) ?? doc.slug;
|
|
1100
|
+
}
|
|
1027
1101
|
params.push({ locale, slug });
|
|
1028
1102
|
}
|
|
1029
1103
|
}
|