glotfile 0.4.0 → 0.4.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/bin/glotfile.js CHANGED
File without changes
@@ -1590,6 +1590,7 @@ function buildSystemPrompt(hasPluralItems) {
1590
1590
  function buildBatchPrompt(reqs) {
1591
1591
  const targetLocale = reqs[0]?.targetLocale ?? "";
1592
1592
  const hasPluralItems = reqs.some((r) => r.plural !== void 0);
1593
+ const hasGlossaryItems = reqs.some((r) => r.glossary !== void 0 && r.glossary.length > 0);
1593
1594
  const items = reqs.map((r) => {
1594
1595
  const base = {
1595
1596
  id: r.id,
@@ -1599,7 +1600,7 @@ function buildBatchPrompt(reqs) {
1599
1600
  // Wrap in braces so the model sees "{site}" not "site" — makes the visual
1600
1601
  // connection to the source string obvious and reduces rename errors.
1601
1602
  placeholders: r.placeholders.map((p) => `{${p}}`),
1602
- glossary: r.glossary ?? [],
1603
+ ...r.glossary?.length ? { glossary: r.glossary } : {},
1603
1604
  hasScreenshot: r.image !== void 0
1604
1605
  };
1605
1606
  if (r.plural) {
@@ -1609,7 +1610,7 @@ function buildBatchPrompt(reqs) {
1609
1610
  });
1610
1611
  const returnFormat = hasPluralItems ? 'For a scalar item (has `source`) return {"id","translation"}; for a plural item (has `plural`) return {"id","forms"} with one string per required category.' : 'Return {"id","translation"} for each item.';
1611
1612
  return `Translate every item below into the target locale: ${targetLocale}. All items share this one target language.
1612
- Glossary entries are constraints you MUST apply. Items with hasScreenshot:true have a screenshot supplied as a separate image block above; use it for context. ${returnFormat} Return JSON {"items":[\u2026]}.
1613
+ ` + (hasGlossaryItems ? "Glossary entries are constraints you MUST apply. " : "") + `Items with hasScreenshot:true have a screenshot supplied as a separate image block above; use it for context. ${returnFormat} Return JSON {"items":[\u2026]}.
1613
1614
  ` + JSON.stringify(items, null, 2);
1614
1615
  }
1615
1616
  function buildTranslateGemmaSystemPrompt(sourceLocale, targetLocale) {
@@ -2929,7 +2930,7 @@ function selectContextTargets(state, opts, cache2, lastRunAt) {
2929
2930
  let candidates = [];
2930
2931
  for (const key of Object.keys(state.keys).sort()) {
2931
2932
  const entry = state.keys[key];
2932
- if (entry.context) continue;
2933
+ if (entry.context && !opts.force) continue;
2933
2934
  if (keySet && !keySet.has(key)) continue;
2934
2935
  if (keyRe && !keyRe.test(key)) continue;
2935
2936
  if (cutoff) {
@@ -2977,7 +2978,7 @@ ${s.lines}
2977
2978
  });
2978
2979
  return 'Write a context note for each key. Return JSON {"items":[{"id","context"}]}.\n' + JSON.stringify(items, null, 2);
2979
2980
  }
2980
- function applyContext(state, reqs, results, clock = systemClock) {
2981
+ function applyContext(state, reqs, results, clock = systemClock, force = false) {
2981
2982
  const byId = new Map(reqs.map((r) => [r.id, r]));
2982
2983
  let written = 0;
2983
2984
  const errors = [];
@@ -2998,7 +2999,7 @@ function applyContext(state, reqs, results, clock = systemClock) {
2998
2999
  continue;
2999
3000
  }
3000
3001
  const entry = state.keys[req.key];
3001
- if (!entry || entry.context) continue;
3002
+ if (!entry || entry.context && !force) continue;
3002
3003
  entry.context = context;
3003
3004
  entry.contextSource = "ai";
3004
3005
  entry.contextAt = clock();
@@ -4470,7 +4471,7 @@ function createApi(deps) {
4470
4471
  await stream.writeSSE({ event: "error", data: JSON.stringify({ error: e.message }) });
4471
4472
  return;
4472
4473
  }
4473
- const { skipped } = attachScreenshotsForProvider(reqs, s, projectRoot, provider.supportsVision());
4474
+ const { skipped } = attachScreenshotsForProvider(reqs, s, dirname2(resolve9(deps.statePath)), provider.supportsVision());
4474
4475
  if (skipped) console.warn(`Model "${aiCfg.model}" has no vision support; ${skipped} screenshot(s) ignored.`);
4475
4476
  console.log(`[translate] ${reqs.length} string(s) \u2192 ${aiCfg.model}`);
4476
4477
  let totalWritten = 0;
@@ -4548,7 +4549,7 @@ function createApi(deps) {
4548
4549
  } catch (e) {
4549
4550
  return c.json({ error: e.message }, 400);
4550
4551
  }
4551
- const { skipped } = attachScreenshotsForProvider(toTranslate, s, projectRoot, provider.supportsVision());
4552
+ const { skipped } = attachScreenshotsForProvider(toTranslate, s, dirname2(resolve9(deps.statePath)), provider.supportsVision());
4552
4553
  if (skipped) console.warn(`Model "${aiCfg.model}" has no vision support; ${skipped} screenshot(s) ignored.`);
4553
4554
  const results = await runLocaleParallel(toTranslate, provider, {}, aiCfg.concurrency);
4554
4555
  const latest = load();
@@ -4643,7 +4644,8 @@ function createApi(deps) {
4643
4644
  keyGlob: body.keyGlob,
4644
4645
  limit: body.limit,
4645
4646
  since: body.since,
4646
- keys: body.keys
4647
+ keys: body.keys,
4648
+ force: body.force
4647
4649
  }, cache2, body.lastRunAt);
4648
4650
  if (!targets.length) {
4649
4651
  await stream.writeSSE({ event: "done", data: JSON.stringify({ requested: 0, written: 0, errors: [] }) });
@@ -4696,7 +4698,7 @@ function createApi(deps) {
4696
4698
  if (signal?.aborted) break;
4697
4699
  const batch = raw;
4698
4700
  const fresh = load();
4699
- const { written, errors } = applyContext(fresh, chunk2, batch.items ?? []);
4701
+ const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
4700
4702
  appendLog(projectRoot, {
4701
4703
  at: (/* @__PURE__ */ new Date()).toISOString(),
4702
4704
  kind: "context",
@@ -4776,12 +4778,12 @@ async function readFileResponse(absPath) {
4776
4778
  }
4777
4779
  function buildApp(opts) {
4778
4780
  const app = new Hono2();
4779
- app.route("/api", createApi({ statePath: opts.statePath, autoExport: true }));
4780
- const projectRoot = dirname3(resolve10(opts.statePath));
4781
+ const apiDeps = { statePath: opts.statePath, autoExport: true };
4782
+ app.route("/api", createApi(apiDeps));
4781
4783
  app.get("/:dir/*", async (c, next) => {
4782
4784
  const dirSeg = c.req.param("dir");
4783
4785
  if (!dirSeg.endsWith("-screenshots")) return next();
4784
- const shotsRoot = resolve10(projectRoot, dirSeg);
4786
+ const shotsRoot = resolve10(dirname3(resolve10(apiDeps.statePath)), dirSeg);
4785
4787
  const pathname = decodeURIComponent(new URL(c.req.url).pathname);
4786
4788
  const rest = pathname.slice(`/${dirSeg}`.length);
4787
4789
  const target = resolve10(shotsRoot, "." + rest);
@@ -1129,7 +1129,7 @@ function selectContextTargets(state, opts, cache2, lastRunAt) {
1129
1129
  let candidates = [];
1130
1130
  for (const key of Object.keys(state.keys).sort()) {
1131
1131
  const entry = state.keys[key];
1132
- if (entry.context) continue;
1132
+ if (entry.context && !opts.force) continue;
1133
1133
  if (keySet && !keySet.has(key)) continue;
1134
1134
  if (keyRe && !keyRe.test(key)) continue;
1135
1135
  if (cutoff) {
@@ -1197,7 +1197,7 @@ var CONTEXT_BATCH_SCHEMA = {
1197
1197
  required: ["items"],
1198
1198
  additionalProperties: false
1199
1199
  };
1200
- function applyContext(state, reqs, results, clock = systemClock) {
1200
+ function applyContext(state, reqs, results, clock = systemClock, force = false) {
1201
1201
  const byId = new Map(reqs.map((r) => [r.id, r]));
1202
1202
  let written = 0;
1203
1203
  const errors = [];
@@ -1218,7 +1218,7 @@ function applyContext(state, reqs, results, clock = systemClock) {
1218
1218
  continue;
1219
1219
  }
1220
1220
  const entry = state.keys[req.key];
1221
- if (!entry || entry.context) continue;
1221
+ if (!entry || entry.context && !force) continue;
1222
1222
  entry.context = context;
1223
1223
  entry.contextSource = "ai";
1224
1224
  entry.contextAt = clock();
@@ -2376,6 +2376,7 @@ function buildSystemPrompt(hasPluralItems) {
2376
2376
  function buildBatchPrompt(reqs) {
2377
2377
  const targetLocale = reqs[0]?.targetLocale ?? "";
2378
2378
  const hasPluralItems = reqs.some((r) => r.plural !== void 0);
2379
+ const hasGlossaryItems = reqs.some((r) => r.glossary !== void 0 && r.glossary.length > 0);
2379
2380
  const items = reqs.map((r) => {
2380
2381
  const base = {
2381
2382
  id: r.id,
@@ -2385,7 +2386,7 @@ function buildBatchPrompt(reqs) {
2385
2386
  // Wrap in braces so the model sees "{site}" not "site" — makes the visual
2386
2387
  // connection to the source string obvious and reduces rename errors.
2387
2388
  placeholders: r.placeholders.map((p) => `{${p}}`),
2388
- glossary: r.glossary ?? [],
2389
+ ...r.glossary?.length ? { glossary: r.glossary } : {},
2389
2390
  hasScreenshot: r.image !== void 0
2390
2391
  };
2391
2392
  if (r.plural) {
@@ -2395,7 +2396,7 @@ function buildBatchPrompt(reqs) {
2395
2396
  });
2396
2397
  const returnFormat = hasPluralItems ? 'For a scalar item (has `source`) return {"id","translation"}; for a plural item (has `plural`) return {"id","forms"} with one string per required category.' : 'Return {"id","translation"} for each item.';
2397
2398
  return `Translate every item below into the target locale: ${targetLocale}. All items share this one target language.
2398
- Glossary entries are constraints you MUST apply. Items with hasScreenshot:true have a screenshot supplied as a separate image block above; use it for context. ${returnFormat} Return JSON {"items":[\u2026]}.
2399
+ ` + (hasGlossaryItems ? "Glossary entries are constraints you MUST apply. " : "") + `Items with hasScreenshot:true have a screenshot supplied as a separate image block above; use it for context. ${returnFormat} Return JSON {"items":[\u2026]}.
2399
2400
  ` + JSON.stringify(items, null, 2);
2400
2401
  }
2401
2402
  function buildTranslateGemmaSystemPrompt(sourceLocale, targetLocale) {
@@ -4100,7 +4101,7 @@ function createApi(deps) {
4100
4101
  await stream.writeSSE({ event: "error", data: JSON.stringify({ error: e.message }) });
4101
4102
  return;
4102
4103
  }
4103
- const { skipped } = attachScreenshotsForProvider(reqs, s, projectRoot, provider.supportsVision());
4104
+ const { skipped } = attachScreenshotsForProvider(reqs, s, dirname2(resolve8(deps.statePath)), provider.supportsVision());
4104
4105
  if (skipped) console.warn(`Model "${aiCfg.model}" has no vision support; ${skipped} screenshot(s) ignored.`);
4105
4106
  console.log(`[translate] ${reqs.length} string(s) \u2192 ${aiCfg.model}`);
4106
4107
  let totalWritten = 0;
@@ -4178,7 +4179,7 @@ function createApi(deps) {
4178
4179
  } catch (e) {
4179
4180
  return c.json({ error: e.message }, 400);
4180
4181
  }
4181
- const { skipped } = attachScreenshotsForProvider(toTranslate, s, projectRoot, provider.supportsVision());
4182
+ const { skipped } = attachScreenshotsForProvider(toTranslate, s, dirname2(resolve8(deps.statePath)), provider.supportsVision());
4182
4183
  if (skipped) console.warn(`Model "${aiCfg.model}" has no vision support; ${skipped} screenshot(s) ignored.`);
4183
4184
  const results = await runLocaleParallel(toTranslate, provider, {}, aiCfg.concurrency);
4184
4185
  const latest = load();
@@ -4273,7 +4274,8 @@ function createApi(deps) {
4273
4274
  keyGlob: body.keyGlob,
4274
4275
  limit: body.limit,
4275
4276
  since: body.since,
4276
- keys: body.keys
4277
+ keys: body.keys,
4278
+ force: body.force
4277
4279
  }, cache2, body.lastRunAt);
4278
4280
  if (!targets.length) {
4279
4281
  await stream.writeSSE({ event: "done", data: JSON.stringify({ requested: 0, written: 0, errors: [] }) });
@@ -4326,7 +4328,7 @@ function createApi(deps) {
4326
4328
  if (signal?.aborted) break;
4327
4329
  const batch = raw;
4328
4330
  const fresh = load();
4329
- const { written, errors } = applyContext(fresh, chunk2, batch.items ?? []);
4331
+ const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
4330
4332
  appendLog(projectRoot, {
4331
4333
  at: (/* @__PURE__ */ new Date()).toISOString(),
4332
4334
  kind: "context",
@@ -4385,12 +4387,12 @@ async function readFileResponse(absPath) {
4385
4387
  }
4386
4388
  function buildApp(opts) {
4387
4389
  const app = new Hono2();
4388
- app.route("/api", createApi({ statePath: opts.statePath, autoExport: true }));
4389
- const projectRoot = dirname3(resolve9(opts.statePath));
4390
+ const apiDeps = { statePath: opts.statePath, autoExport: true };
4391
+ app.route("/api", createApi(apiDeps));
4390
4392
  app.get("/:dir/*", async (c, next) => {
4391
4393
  const dirSeg = c.req.param("dir");
4392
4394
  if (!dirSeg.endsWith("-screenshots")) return next();
4393
- const shotsRoot = resolve9(projectRoot, dirSeg);
4395
+ const shotsRoot = resolve9(dirname3(resolve9(apiDeps.statePath)), dirSeg);
4394
4396
  const pathname = decodeURIComponent(new URL(c.req.url).pathname);
4395
4397
  const rest = pathname.slice(`/${dirSeg}`.length);
4396
4398
  const target = resolve9(shotsRoot, "." + rest);