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.js
CHANGED
|
@@ -127,6 +127,7 @@ function resolveConfig(input, baseDir) {
|
|
|
127
127
|
if (localeRouting.strategy === "search-param" && !localeRouting.param) {
|
|
128
128
|
throw new Error('scribe config: localeRouting search-param requires a "param" name');
|
|
129
129
|
}
|
|
130
|
+
const localeFallbacks = raw.localeFallbacks === false ? {} : deriveLocaleFallbacks(raw.locales, defaultLocale);
|
|
130
131
|
const projectRoot = path.resolve(process.cwd(), raw.rootDir);
|
|
131
132
|
const contentRoot = path.resolve(projectRoot, raw.contentDir ?? "content");
|
|
132
133
|
const storePath = path.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
@@ -156,6 +157,7 @@ function resolveConfig(input, baseDir) {
|
|
|
156
157
|
defaultLocale,
|
|
157
158
|
localeRouting,
|
|
158
159
|
localePresets: raw.localePresets,
|
|
160
|
+
localeFallbacks,
|
|
159
161
|
translate: raw.translate,
|
|
160
162
|
types
|
|
161
163
|
};
|
|
@@ -165,6 +167,25 @@ function resolveConfig(input, baseDir) {
|
|
|
165
167
|
});
|
|
166
168
|
return config;
|
|
167
169
|
}
|
|
170
|
+
function deriveLocaleFallbacks(locales, defaultLocale) {
|
|
171
|
+
const byLowercase = new Map(locales.map((l) => [l.toLowerCase(), l]));
|
|
172
|
+
const fallbacks = {};
|
|
173
|
+
for (const locale of locales) {
|
|
174
|
+
const subtags = locale.split("-");
|
|
175
|
+
if (subtags.length < 2) continue;
|
|
176
|
+
const chain = [];
|
|
177
|
+
for (let end = subtags.length - 1; end >= 1; end--) {
|
|
178
|
+
const prefix = byLowercase.get(subtags.slice(0, end).join("-").toLowerCase());
|
|
179
|
+
if (prefix && prefix !== locale && prefix !== defaultLocale) {
|
|
180
|
+
chain.push(prefix);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (chain.length > 0) {
|
|
184
|
+
fallbacks[locale] = chain;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return fallbacks;
|
|
188
|
+
}
|
|
168
189
|
var FIELD_KIND = /* @__PURE__ */ Symbol.for("@genlook/scribe/fieldKind");
|
|
169
190
|
var RELATION_META = /* @__PURE__ */ Symbol.for("@genlook/scribe/relationMeta");
|
|
170
191
|
function getFieldKind(schema) {
|
|
@@ -194,20 +215,16 @@ function getRelationTarget(schema) {
|
|
|
194
215
|
}
|
|
195
216
|
return null;
|
|
196
217
|
}
|
|
197
|
-
function
|
|
218
|
+
function peelOptionalWrappers(schema) {
|
|
198
219
|
let current = schema;
|
|
199
220
|
for (let i = 0; i < 8; i++) {
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
current =
|
|
221
|
+
const type = current._def?.type;
|
|
222
|
+
if (type === "optional" || type === "nullable") {
|
|
223
|
+
current = current.unwrap();
|
|
203
224
|
continue;
|
|
204
225
|
}
|
|
205
|
-
if (
|
|
206
|
-
current =
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (anySchema._def?.innerType) {
|
|
210
|
-
current = anySchema._def.innerType;
|
|
226
|
+
if (type === "default") {
|
|
227
|
+
current = current.removeDefault();
|
|
211
228
|
continue;
|
|
212
229
|
}
|
|
213
230
|
break;
|
|
@@ -216,6 +233,12 @@ function unwrapSchema(schema) {
|
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
// src/core/introspect-schema.ts
|
|
236
|
+
function getArrayElement(schema) {
|
|
237
|
+
if (schema instanceof Object && "element" in schema && schema._def?.type === "array") {
|
|
238
|
+
return schema.element;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
219
242
|
function introspectSchema(schema, prefix = []) {
|
|
220
243
|
const relation = getRelationTarget(schema);
|
|
221
244
|
if (relation && prefix.length > 0) {
|
|
@@ -229,7 +252,10 @@ function introspectSchema(schema, prefix = []) {
|
|
|
229
252
|
}
|
|
230
253
|
];
|
|
231
254
|
}
|
|
232
|
-
|
|
255
|
+
if (prefix.length > 0 && getFieldKind(schema) === "translatable") {
|
|
256
|
+
return [{ path: prefix, kind: "translatable" }];
|
|
257
|
+
}
|
|
258
|
+
const base = peelOptionalWrappers(schema);
|
|
233
259
|
if (base instanceof Object && "shape" in base) {
|
|
234
260
|
const shape = base.shape;
|
|
235
261
|
const fields = [];
|
|
@@ -238,9 +264,9 @@ function introspectSchema(schema, prefix = []) {
|
|
|
238
264
|
}
|
|
239
265
|
return fields;
|
|
240
266
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const elementBase =
|
|
267
|
+
const element = getArrayElement(base);
|
|
268
|
+
if (element) {
|
|
269
|
+
const elementBase = peelOptionalWrappers(element);
|
|
244
270
|
if (elementBase instanceof Object && "shape" in elementBase) {
|
|
245
271
|
const shape = elementBase.shape;
|
|
246
272
|
const fields = [];
|
|
@@ -252,15 +278,48 @@ function introspectSchema(schema, prefix = []) {
|
|
|
252
278
|
}
|
|
253
279
|
return [{ path: prefix, kind: getFieldKind(schema) }];
|
|
254
280
|
}
|
|
255
|
-
function
|
|
256
|
-
const
|
|
281
|
+
function buildPathTrie(paths) {
|
|
282
|
+
const root = { leaf: false, children: /* @__PURE__ */ new Map() };
|
|
257
283
|
for (const path6 of paths) {
|
|
258
|
-
|
|
284
|
+
let node = root;
|
|
285
|
+
for (const segment of path6) {
|
|
286
|
+
let child = node.children.get(segment);
|
|
287
|
+
if (!child) {
|
|
288
|
+
child = { leaf: false, children: /* @__PURE__ */ new Map() };
|
|
289
|
+
node.children.set(segment, child);
|
|
290
|
+
}
|
|
291
|
+
node = child;
|
|
292
|
+
}
|
|
293
|
+
node.leaf = true;
|
|
294
|
+
}
|
|
295
|
+
return root;
|
|
296
|
+
}
|
|
297
|
+
function extractNode(data, trie) {
|
|
298
|
+
if (trie.leaf) return data;
|
|
299
|
+
const star = trie.children.get("*");
|
|
300
|
+
if (star) {
|
|
301
|
+
if (!Array.isArray(data)) return void 0;
|
|
302
|
+
return data.map(
|
|
303
|
+
(item) => typeof item === "object" && item !== null ? extractNode(item, star) ?? {} : {}
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return void 0;
|
|
307
|
+
const record = data;
|
|
308
|
+
const out = {};
|
|
309
|
+
let any = false;
|
|
310
|
+
for (const [key, child] of trie.children) {
|
|
311
|
+
const value = extractNode(record[key], child);
|
|
259
312
|
if (value !== void 0) {
|
|
260
|
-
|
|
313
|
+
out[key] = value;
|
|
314
|
+
any = true;
|
|
261
315
|
}
|
|
262
316
|
}
|
|
263
|
-
return out;
|
|
317
|
+
return any ? out : void 0;
|
|
318
|
+
}
|
|
319
|
+
function extractByPaths(data, paths) {
|
|
320
|
+
if (paths.length === 0) return {};
|
|
321
|
+
const extracted = extractNode(data, buildPathTrie(paths));
|
|
322
|
+
return extracted && typeof extracted === "object" && !Array.isArray(extracted) ? extracted : {};
|
|
264
323
|
}
|
|
265
324
|
function pickTranslatable(data, schema) {
|
|
266
325
|
const translatablePaths = introspectSchema(schema).filter((f) => f.kind === "translatable").map((f) => f.path);
|
|
@@ -275,32 +334,6 @@ function mergeStructuralOntoLocale(localeData, enData, schema) {
|
|
|
275
334
|
const translatable = pickTranslatable(localeData, schema);
|
|
276
335
|
return deepMerge(structural, translatable);
|
|
277
336
|
}
|
|
278
|
-
function getAtPath(obj, path6) {
|
|
279
|
-
let current = obj;
|
|
280
|
-
for (const segment of path6) {
|
|
281
|
-
if (segment === "*") {
|
|
282
|
-
if (!Array.isArray(current)) return void 0;
|
|
283
|
-
return current.map(
|
|
284
|
-
(item) => typeof item === "object" && item !== null ? getAtPath(item, path6.slice(path6.indexOf("*") + 1)) : void 0
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
288
|
-
current = current[segment];
|
|
289
|
-
}
|
|
290
|
-
return current;
|
|
291
|
-
}
|
|
292
|
-
function setAtPath(obj, path6, value) {
|
|
293
|
-
if (path6.length === 0) return;
|
|
294
|
-
if (path6.length === 1) {
|
|
295
|
-
obj[path6[0]] = value;
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const [head, ...rest] = path6;
|
|
299
|
-
if (!(head in obj) || typeof obj[head] !== "object" || obj[head] === null) {
|
|
300
|
-
obj[head] = {};
|
|
301
|
-
}
|
|
302
|
-
setAtPath(obj[head], rest, value);
|
|
303
|
-
}
|
|
304
337
|
function deepMerge(base, overlay) {
|
|
305
338
|
const out = { ...base };
|
|
306
339
|
for (const [key, value] of Object.entries(overlay)) {
|
|
@@ -451,7 +484,7 @@ function seoFieldsFromEn(enDoc) {
|
|
|
451
484
|
canonicalPathOverride: enDoc.canonicalPathOverride
|
|
452
485
|
};
|
|
453
486
|
}
|
|
454
|
-
var SCHEMA_VERSION =
|
|
487
|
+
var SCHEMA_VERSION = 5;
|
|
455
488
|
var MIGRATIONS = [
|
|
456
489
|
`CREATE TABLE IF NOT EXISTS meta (
|
|
457
490
|
key TEXT PRIMARY KEY,
|
|
@@ -499,7 +532,30 @@ var MIGRATIONS = [
|
|
|
499
532
|
UNIQUE (content_type, en_slug, en_hash)
|
|
500
533
|
)`,
|
|
501
534
|
`CREATE INDEX IF NOT EXISTS idx_en_snapshots_lookup
|
|
502
|
-
ON en_snapshots(content_type, en_slug, created_at DESC)
|
|
535
|
+
ON en_snapshots(content_type, en_slug, created_at DESC)`,
|
|
536
|
+
`CREATE TABLE IF NOT EXISTS translation_batch_jobs (
|
|
537
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
538
|
+
job_name TEXT NOT NULL UNIQUE,
|
|
539
|
+
model TEXT NOT NULL,
|
|
540
|
+
display_model TEXT NOT NULL,
|
|
541
|
+
created_at TEXT NOT NULL,
|
|
542
|
+
state TEXT NOT NULL,
|
|
543
|
+
completed_at TEXT
|
|
544
|
+
)`,
|
|
545
|
+
`CREATE TABLE IF NOT EXISTS translation_batch_items (
|
|
546
|
+
job_id INTEGER NOT NULL,
|
|
547
|
+
request_index INTEGER NOT NULL,
|
|
548
|
+
content_type TEXT NOT NULL,
|
|
549
|
+
en_slug TEXT NOT NULL,
|
|
550
|
+
locale TEXT NOT NULL,
|
|
551
|
+
en_hash TEXT NOT NULL,
|
|
552
|
+
snapshot_id INTEGER NOT NULL,
|
|
553
|
+
status TEXT NOT NULL,
|
|
554
|
+
error TEXT,
|
|
555
|
+
PRIMARY KEY (job_id, request_index)
|
|
556
|
+
)`,
|
|
557
|
+
`CREATE INDEX IF NOT EXISTS idx_batch_items_status
|
|
558
|
+
ON translation_batch_items(status)`
|
|
503
559
|
];
|
|
504
560
|
function resolveStorePath(config) {
|
|
505
561
|
return config.storePath;
|
|
@@ -522,6 +578,8 @@ function addColumnIfMissing(db, table, column, ddlType) {
|
|
|
522
578
|
}
|
|
523
579
|
}
|
|
524
580
|
function readSchemaVersion(db) {
|
|
581
|
+
const metaTable = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'meta'`).get();
|
|
582
|
+
if (!metaTable) return 0;
|
|
525
583
|
const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
|
|
526
584
|
return row ? Number.parseInt(row.value, 10) || 0 : 0;
|
|
527
585
|
}
|
|
@@ -582,7 +640,8 @@ function sanitizeMdxJsxAttributeQuotes(body) {
|
|
|
582
640
|
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
583
641
|
const attrStart = i;
|
|
584
642
|
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
585
|
-
body.slice(attrStart, i);
|
|
643
|
+
const attrName = body.slice(attrStart, i);
|
|
644
|
+
if (!attrName) break;
|
|
586
645
|
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
587
646
|
if (body[i] !== "=") {
|
|
588
647
|
tagOut += body.slice(attrStart, i);
|
|
@@ -591,10 +650,12 @@ function sanitizeMdxJsxAttributeQuotes(body) {
|
|
|
591
650
|
tagOut += body.slice(attrStart, i);
|
|
592
651
|
tagOut += "=";
|
|
593
652
|
i += 1;
|
|
653
|
+
const wsStart = i;
|
|
594
654
|
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
655
|
+
tagOut += body.slice(wsStart, i);
|
|
595
656
|
const quote = body[i];
|
|
596
657
|
if (quote !== '"' && quote !== "'") {
|
|
597
|
-
|
|
658
|
+
break;
|
|
598
659
|
}
|
|
599
660
|
if (quote === "'") {
|
|
600
661
|
const valStart2 = i + 1;
|
|
@@ -877,7 +938,7 @@ function createContentLoader(config, type) {
|
|
|
877
938
|
}
|
|
878
939
|
|
|
879
940
|
// src/i18n/resolve-document.ts
|
|
880
|
-
function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, localeRouting) {
|
|
941
|
+
function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, localeRouting, fallbackLocales = []) {
|
|
881
942
|
const urlBuilder = createUrlBuilder({
|
|
882
943
|
locales: [defaultLocale, locale],
|
|
883
944
|
defaultLocale,
|
|
@@ -891,15 +952,28 @@ function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, lo
|
|
|
891
952
|
for (const [docLocale, docIdx] of allDocs) {
|
|
892
953
|
const found = docIdx.bySlug.get(slug);
|
|
893
954
|
if (!found) continue;
|
|
894
|
-
const
|
|
895
|
-
|
|
955
|
+
const enSlug = docLocale === defaultLocale ? found.slug : found.enSlug;
|
|
956
|
+
let target;
|
|
957
|
+
let targetLocale = locale;
|
|
958
|
+
for (const candidateLocale of [locale, ...fallbackLocales]) {
|
|
959
|
+
const cand = candidateLocale === defaultLocale ? allDocs.get(candidateLocale)?.bySlug.get(enSlug) : allDocs.get(candidateLocale)?.byEnSlug.get(enSlug);
|
|
960
|
+
if (cand) {
|
|
961
|
+
target = cand;
|
|
962
|
+
targetLocale = candidateLocale;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (target) {
|
|
967
|
+
if (target.slug === slug) {
|
|
968
|
+
return { document: target, actualLocale: targetLocale };
|
|
969
|
+
}
|
|
896
970
|
if (!type.path) {
|
|
897
971
|
return { document: null, actualLocale: locale };
|
|
898
972
|
}
|
|
899
973
|
return {
|
|
900
974
|
document: null,
|
|
901
975
|
actualLocale: locale,
|
|
902
|
-
shouldRedirectTo: urlBuilder.resolvePath(type.path,
|
|
976
|
+
shouldRedirectTo: urlBuilder.resolvePath(type.path, target.slug, locale)
|
|
903
977
|
};
|
|
904
978
|
}
|
|
905
979
|
if (docLocale === defaultLocale) {
|
|
@@ -930,13 +1004,6 @@ function resolveLocalizedDocument(slug, locale, defaultLocale, allDocs, type, lo
|
|
|
930
1004
|
}
|
|
931
1005
|
return { document: null, actualLocale: locale };
|
|
932
1006
|
}
|
|
933
|
-
function getSlugForLocale(document, sourceLocale, targetLocale, allDocs, defaultLocale) {
|
|
934
|
-
if (sourceLocale === targetLocale) return document.slug;
|
|
935
|
-
const englishSlug = sourceLocale === defaultLocale ? document.slug : document.enSlug;
|
|
936
|
-
if (!englishSlug) return null;
|
|
937
|
-
if (targetLocale === defaultLocale) return englishSlug;
|
|
938
|
-
return allDocs.get(targetLocale)?.byEnSlug.get(englishSlug)?.slug ?? null;
|
|
939
|
-
}
|
|
940
1007
|
|
|
941
1008
|
// src/create-project.ts
|
|
942
1009
|
function comparatorFor(orderBy) {
|
|
@@ -989,7 +1056,8 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
989
1056
|
config.defaultLocale,
|
|
990
1057
|
load(),
|
|
991
1058
|
type,
|
|
992
|
-
config.localeRouting
|
|
1059
|
+
config.localeRouting,
|
|
1060
|
+
config.localeFallbacks[locale] ?? []
|
|
993
1061
|
);
|
|
994
1062
|
if (result.document && type.path) {
|
|
995
1063
|
return {
|
|
@@ -1011,8 +1079,14 @@ function buildRuntime(config, type, getRuntime) {
|
|
|
1011
1079
|
const params = [];
|
|
1012
1080
|
for (const locale of options.locales ?? config.locales) {
|
|
1013
1081
|
const localeIdx = all.get(locale);
|
|
1082
|
+
const fallbacks = config.localeFallbacks[locale] ?? [];
|
|
1014
1083
|
for (const doc of enIdx.bySlug.values()) {
|
|
1015
|
-
|
|
1084
|
+
let slug;
|
|
1085
|
+
if (locale === config.defaultLocale) {
|
|
1086
|
+
slug = doc.slug;
|
|
1087
|
+
} else {
|
|
1088
|
+
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;
|
|
1089
|
+
}
|
|
1016
1090
|
params.push({ locale, slug });
|
|
1017
1091
|
}
|
|
1018
1092
|
}
|