@hkonda/loco-translate 1.0.0

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.
Files changed (92) hide show
  1. package/README.md +284 -0
  2. package/bin/loco.js +5 -0
  3. package/dist/assets/index-CGo6e-bA.js +59 -0
  4. package/dist/assets/index-DBcQDZ75.css +1 -0
  5. package/dist/index.html +13 -0
  6. package/dist-server/app.d.ts +3 -0
  7. package/dist-server/app.d.ts.map +1 -0
  8. package/dist-server/app.js +154 -0
  9. package/dist-server/app.js.map +1 -0
  10. package/dist-server/config.d.ts +16 -0
  11. package/dist-server/config.d.ts.map +1 -0
  12. package/dist-server/config.js +41 -0
  13. package/dist-server/config.js.map +1 -0
  14. package/dist-server/db/index.d.ts +8 -0
  15. package/dist-server/db/index.d.ts.map +1 -0
  16. package/dist-server/db/index.js +28 -0
  17. package/dist-server/db/index.js.map +1 -0
  18. package/dist-server/db/schema.d.ts +851 -0
  19. package/dist-server/db/schema.d.ts.map +1 -0
  20. package/dist-server/db/schema.js +65 -0
  21. package/dist-server/db/schema.js.map +1 -0
  22. package/dist-server/db/seed.d.ts +14 -0
  23. package/dist-server/db/seed.d.ts.map +1 -0
  24. package/dist-server/db/seed.js +229 -0
  25. package/dist-server/db/seed.js.map +1 -0
  26. package/dist-server/index.d.ts +2 -0
  27. package/dist-server/index.d.ts.map +1 -0
  28. package/dist-server/index.js +31 -0
  29. package/dist-server/index.js.map +1 -0
  30. package/dist-server/routes/ai-jobs.d.ts +5 -0
  31. package/dist-server/routes/ai-jobs.d.ts.map +1 -0
  32. package/dist-server/routes/ai-jobs.js +141 -0
  33. package/dist-server/routes/ai-jobs.js.map +1 -0
  34. package/dist-server/routes/backup.d.ts +5 -0
  35. package/dist-server/routes/backup.d.ts.map +1 -0
  36. package/dist-server/routes/backup.js +125 -0
  37. package/dist-server/routes/backup.js.map +1 -0
  38. package/dist-server/routes/chrome-extension.d.ts +5 -0
  39. package/dist-server/routes/chrome-extension.d.ts.map +1 -0
  40. package/dist-server/routes/chrome-extension.js +140 -0
  41. package/dist-server/routes/chrome-extension.js.map +1 -0
  42. package/dist-server/routes/export.d.ts +5 -0
  43. package/dist-server/routes/export.d.ts.map +1 -0
  44. package/dist-server/routes/export.js +95 -0
  45. package/dist-server/routes/export.js.map +1 -0
  46. package/dist-server/routes/languages.d.ts +5 -0
  47. package/dist-server/routes/languages.d.ts.map +1 -0
  48. package/dist-server/routes/languages.js +36 -0
  49. package/dist-server/routes/languages.js.map +1 -0
  50. package/dist-server/routes/project.d.ts +5 -0
  51. package/dist-server/routes/project.d.ts.map +1 -0
  52. package/dist-server/routes/project.js +151 -0
  53. package/dist-server/routes/project.js.map +1 -0
  54. package/dist-server/routes/prompts.d.ts +5 -0
  55. package/dist-server/routes/prompts.d.ts.map +1 -0
  56. package/dist-server/routes/prompts.js +90 -0
  57. package/dist-server/routes/prompts.js.map +1 -0
  58. package/dist-server/routes/screenshots.d.ts +5 -0
  59. package/dist-server/routes/screenshots.d.ts.map +1 -0
  60. package/dist-server/routes/screenshots.js +71 -0
  61. package/dist-server/routes/screenshots.js.map +1 -0
  62. package/dist-server/routes/textnodes.d.ts +5 -0
  63. package/dist-server/routes/textnodes.d.ts.map +1 -0
  64. package/dist-server/routes/textnodes.js +318 -0
  65. package/dist-server/routes/textnodes.js.map +1 -0
  66. package/dist-server/routes/translations.d.ts +5 -0
  67. package/dist-server/routes/translations.d.ts.map +1 -0
  68. package/dist-server/routes/translations.js +224 -0
  69. package/dist-server/routes/translations.js.map +1 -0
  70. package/dist-server/scripts/backup.d.ts +2 -0
  71. package/dist-server/scripts/backup.d.ts.map +1 -0
  72. package/dist-server/scripts/backup.js +26 -0
  73. package/dist-server/scripts/backup.js.map +1 -0
  74. package/dist-server/services/ai-translation.d.ts +21 -0
  75. package/dist-server/services/ai-translation.d.ts.map +1 -0
  76. package/dist-server/services/ai-translation.js +137 -0
  77. package/dist-server/services/ai-translation.js.map +1 -0
  78. package/dist-server/services/job-manager.d.ts +67 -0
  79. package/dist-server/services/job-manager.d.ts.map +1 -0
  80. package/dist-server/services/job-manager.js +159 -0
  81. package/dist-server/services/job-manager.js.map +1 -0
  82. package/dist-server/services/slot-pattern.d.ts +16 -0
  83. package/dist-server/services/slot-pattern.d.ts.map +1 -0
  84. package/dist-server/services/slot-pattern.js +68 -0
  85. package/dist-server/services/slot-pattern.js.map +1 -0
  86. package/dist-server/utils/network.d.ts +2 -0
  87. package/dist-server/utils/network.d.ts.map +1 -0
  88. package/dist-server/utils/network.js +16 -0
  89. package/dist-server/utils/network.js.map +1 -0
  90. package/package.json +47 -0
  91. package/public/loco.js +2386 -0
  92. package/public/loco.min.js +4 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../server/db/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMnB,CAAC;AAEH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWpB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASvB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQ1B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS1B,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAanB,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core';
2
+ import { sql } from 'drizzle-orm';
3
+ export const projects = sqliteTable('projects', {
4
+ id: integer('id').primaryKey({ autoIncrement: true }),
5
+ name: text('name').notNull(),
6
+ api_key: text('api_key').notNull().unique(),
7
+ settings: text('settings').notNull().default('{}'),
8
+ created_at: text('created_at').notNull().default(sql `(datetime('now'))`),
9
+ });
10
+ export const textnodes = sqliteTable('textnodes', {
11
+ id: integer('id').primaryKey({ autoIncrement: true }),
12
+ project_id: integer('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
13
+ key: text('key').notNull(),
14
+ context: text('context').notNull().default(''),
15
+ url: text('url').notNull().default(''),
16
+ status: text('status').notNull().default('pending'),
17
+ var_slots: text('var_slots').notNull().default('[]'),
18
+ created_at: text('created_at').notNull().default(sql `(datetime('now'))`),
19
+ }, (table) => [
20
+ uniqueIndex('uq_textnodes_key_ctx').on(table.project_id, table.key, table.context),
21
+ ]);
22
+ export const translations = sqliteTable('translations', {
23
+ id: integer('id').primaryKey({ autoIncrement: true }),
24
+ project_id: integer('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
25
+ lang: text('lang').notNull().default('zh-Hans'),
26
+ key: text('key').notNull(),
27
+ context: text('context').notNull().default(''),
28
+ value: text('value').notNull(),
29
+ }, (table) => [
30
+ uniqueIndex('uq_translations_key_ctx').on(table.project_id, table.lang, table.key, table.context),
31
+ ]);
32
+ export const pageScreenshots = sqliteTable('page_screenshots', {
33
+ id: integer('id').primaryKey({ autoIncrement: true }),
34
+ project_id: integer('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
35
+ url: text('url').notNull(),
36
+ screenshot: text('screenshot').notNull(),
37
+ captured_at: text('captured_at').notNull().default(sql `(datetime('now'))`),
38
+ }, (table) => [
39
+ uniqueIndex('uq_page_screenshots_url').on(table.project_id, table.url),
40
+ ]);
41
+ export const promptTemplates = sqliteTable('prompt_templates', {
42
+ id: text('id').primaryKey(),
43
+ project_id: integer('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
44
+ name: text('name').notNull(),
45
+ prompt: text('prompt').notNull(),
46
+ system_message: text('system_message').notNull().default(''),
47
+ version: integer('version').notNull().default(1),
48
+ created_at: text('created_at').notNull().default(sql `(datetime('now'))`),
49
+ updated_at: text('updated_at').notNull().default(sql `(datetime('now'))`),
50
+ });
51
+ export const aiJobLog = sqliteTable('ai_job_log', {
52
+ id: integer('id').primaryKey({ autoIncrement: true }),
53
+ project_id: integer('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
54
+ job_id: text('job_id').notNull(),
55
+ prompt_id: text('prompt_id'),
56
+ prompt_name: text('prompt_name'),
57
+ lang: text('lang').notNull(),
58
+ total: integer('total').notNull().default(0),
59
+ completed: integer('completed').notNull().default(0),
60
+ failed: integer('failed').notNull().default(0),
61
+ status: text('status').notNull().default('pending'),
62
+ created_at: text('created_at').notNull().default(sql `(datetime('now'))`),
63
+ finished_at: text('finished_at'),
64
+ });
65
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../server/db/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Initialize database tables and run migrations.
3
+ * This handles both fresh installs and upgrades from older schemas.
4
+ */
5
+ export declare function initializeDatabase(): void;
6
+ /**
7
+ * Ensure a default project exists. Returns the project row.
8
+ */
9
+ export declare function ensureDefaultProject(): any;
10
+ /**
11
+ * Reload project from DB (after updates).
12
+ */
13
+ export declare function reloadProject(projectId: number): any;
14
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../server/db/seed.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,kBAAkB,SAkFjC;AA+ID;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,GAAG,CAQ1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,CAEpD"}
@@ -0,0 +1,229 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { rawDb } from './index.js';
3
+ /**
4
+ * Initialize database tables and run migrations.
5
+ * This handles both fresh installs and upgrades from older schemas.
6
+ */
7
+ export function initializeDatabase() {
8
+ // Create tables if they don't exist
9
+ rawDb.exec(`
10
+ CREATE TABLE IF NOT EXISTS projects (
11
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ name TEXT NOT NULL,
13
+ api_key TEXT NOT NULL UNIQUE,
14
+ settings TEXT NOT NULL DEFAULT '{}',
15
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS textnodes (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ project_id INTEGER NOT NULL,
21
+ key TEXT NOT NULL,
22
+ context TEXT NOT NULL DEFAULT '',
23
+ url TEXT NOT NULL DEFAULT '',
24
+ status TEXT NOT NULL DEFAULT 'pending',
25
+ var_slots TEXT NOT NULL DEFAULT '[]',
26
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
27
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS translations (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ project_id INTEGER NOT NULL,
33
+ lang TEXT NOT NULL DEFAULT 'zh-Hans',
34
+ key TEXT NOT NULL,
35
+ context TEXT NOT NULL DEFAULT '',
36
+ value TEXT NOT NULL,
37
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS page_screenshots (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ project_id INTEGER NOT NULL,
43
+ url TEXT NOT NULL,
44
+ screenshot TEXT NOT NULL,
45
+ captured_at TEXT NOT NULL DEFAULT (datetime('now')),
46
+ UNIQUE(project_id, url),
47
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS prompt_templates (
51
+ id TEXT PRIMARY KEY,
52
+ project_id INTEGER NOT NULL,
53
+ name TEXT NOT NULL,
54
+ prompt TEXT NOT NULL,
55
+ system_message TEXT NOT NULL DEFAULT '',
56
+ version INTEGER NOT NULL DEFAULT 1,
57
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
58
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
59
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
60
+ );
61
+
62
+ CREATE TABLE IF NOT EXISTS ai_job_log (
63
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
64
+ project_id INTEGER NOT NULL,
65
+ job_id TEXT NOT NULL,
66
+ prompt_id TEXT,
67
+ prompt_name TEXT,
68
+ lang TEXT NOT NULL,
69
+ total INTEGER NOT NULL DEFAULT 0,
70
+ completed INTEGER NOT NULL DEFAULT 0,
71
+ failed INTEGER NOT NULL DEFAULT 0,
72
+ status TEXT NOT NULL DEFAULT 'pending',
73
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
74
+ finished_at TEXT,
75
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
76
+ );
77
+ `);
78
+ // ── Migrations for existing databases
79
+ const neededMigration = runMigrations();
80
+ // ── Deduplicate textnodes (one-time cleanup for pre-context schema)
81
+ if (neededMigration) {
82
+ runDeduplication();
83
+ }
84
+ // ── Ensure indexes exist
85
+ ensureIndexes();
86
+ }
87
+ function runMigrations() {
88
+ const columns = rawDb.prepare("PRAGMA table_info('textnodes')").all().map((c) => c.name);
89
+ if (!columns.includes('status')) {
90
+ rawDb.exec("ALTER TABLE textnodes ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'");
91
+ }
92
+ if (!columns.includes('url')) {
93
+ rawDb.exec("ALTER TABLE textnodes ADD COLUMN url TEXT NOT NULL DEFAULT ''");
94
+ }
95
+ if (!columns.includes('var_slots')) {
96
+ rawDb.exec("ALTER TABLE textnodes ADD COLUMN var_slots TEXT NOT NULL DEFAULT '[]'");
97
+ }
98
+ const projColumns = rawDb.prepare("PRAGMA table_info('projects')").all().map((c) => c.name);
99
+ if (!projColumns.includes('settings')) {
100
+ rawDb.exec("ALTER TABLE projects ADD COLUMN settings TEXT NOT NULL DEFAULT '{}'");
101
+ }
102
+ // Context-aware key migration
103
+ // Re-read columns after ALTERs above
104
+ const tnCols = rawDb.prepare("PRAGMA table_info('textnodes')").all().map((c) => c.name);
105
+ const txCols = rawDb.prepare("PRAGMA table_info('translations')").all().map((c) => c.name);
106
+ const tnNeedsContext = !tnCols.includes('context');
107
+ const txNeedsContext = !txCols.includes('context');
108
+ const tnIndexes = rawDb.prepare("PRAGMA index_list('textnodes')").all();
109
+ const hasOldTnUnique = tnIndexes.some((idx) => {
110
+ if (!idx.unique)
111
+ return false;
112
+ const cols = rawDb.prepare(`PRAGMA index_info('${idx.name}')`).all().map((c) => c.name);
113
+ return cols.length === 2 && cols.includes('project_id') && cols.includes('key');
114
+ });
115
+ if (tnNeedsContext || txNeedsContext || hasOldTnUnique) {
116
+ // Build SELECT expressions based on which columns actually exist
117
+ const tnContextExpr = tnCols.includes('context') ? "COALESCE(context, '')" : "''";
118
+ const tnCreatedAtExpr = tnCols.includes('created_at') ? 'created_at' : "datetime('now')";
119
+ const txContextExpr = txCols.includes('context') ? "COALESCE(context, '')" : "''";
120
+ rawDb.exec(`
121
+ CREATE TABLE IF NOT EXISTS textnodes_new (
122
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
123
+ project_id INTEGER NOT NULL,
124
+ key TEXT NOT NULL,
125
+ context TEXT NOT NULL DEFAULT '',
126
+ url TEXT NOT NULL DEFAULT '',
127
+ status TEXT NOT NULL DEFAULT 'pending',
128
+ var_slots TEXT NOT NULL DEFAULT '[]',
129
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
130
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
131
+ );
132
+
133
+ INSERT OR IGNORE INTO textnodes_new (id, project_id, key, context, url, status, var_slots, created_at)
134
+ SELECT id, project_id, key,
135
+ ${tnContextExpr}, url, status,
136
+ var_slots, ${tnCreatedAtExpr}
137
+ FROM textnodes;
138
+
139
+ DROP TABLE textnodes;
140
+ ALTER TABLE textnodes_new RENAME TO textnodes;
141
+
142
+ CREATE UNIQUE INDEX uq_textnodes_key_ctx
143
+ ON textnodes (project_id, key, context);
144
+
145
+ CREATE TABLE IF NOT EXISTS translations_new (
146
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147
+ project_id INTEGER NOT NULL,
148
+ lang TEXT NOT NULL DEFAULT 'zh-Hans',
149
+ key TEXT NOT NULL,
150
+ context TEXT NOT NULL DEFAULT '',
151
+ value TEXT NOT NULL,
152
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
153
+ );
154
+
155
+ INSERT OR IGNORE INTO translations_new (id, project_id, lang, key, context, value)
156
+ SELECT id, project_id, lang, key,
157
+ ${txContextExpr}, value
158
+ FROM translations;
159
+
160
+ DROP TABLE translations;
161
+ ALTER TABLE translations_new RENAME TO translations;
162
+
163
+ CREATE UNIQUE INDEX uq_translations_key_ctx
164
+ ON translations (project_id, lang, key, context);
165
+ `);
166
+ return true;
167
+ }
168
+ return false;
169
+ }
170
+ function ensureIndexes() {
171
+ try {
172
+ rawDb.exec(`
173
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_textnodes_key_ctx
174
+ ON textnodes (project_id, key, context);
175
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_translations_key_ctx
176
+ ON translations (project_id, lang, key, context);
177
+ `);
178
+ }
179
+ catch { /* already exist */ }
180
+ }
181
+ function runDeduplication() {
182
+ const dupeKeys = rawDb.prepare(`
183
+ SELECT project_id, key, COUNT(*) AS cnt
184
+ FROM textnodes
185
+ GROUP BY project_id, key
186
+ HAVING cnt > 1
187
+ `).all();
188
+ if (dupeKeys.length === 0)
189
+ return;
190
+ console.log(`[db] Deduplicating ${dupeKeys.length} key(s) with multiple contexts…`);
191
+ const findRows = rawDb.prepare('SELECT id, context FROM textnodes WHERE project_id = ? AND key = ? ORDER BY id ASC');
192
+ const updateTrans = rawDb.prepare('UPDATE OR IGNORE translations SET context = ? WHERE project_id = ? AND key = ? AND context = ?');
193
+ const deleteRow = rawDb.prepare('DELETE FROM textnodes WHERE id = ?');
194
+ const deleteOrphanTrans = rawDb.prepare('DELETE FROM translations WHERE project_id = ? AND key = ? AND context = ?');
195
+ rawDb.transaction(() => {
196
+ for (const { project_id, key } of dupeKeys) {
197
+ const rows = findRows.all(project_id, key);
198
+ if (rows.length <= 1)
199
+ continue;
200
+ const keep = rows[0];
201
+ for (let i = 1; i < rows.length; i++) {
202
+ const dupe = rows[i];
203
+ updateTrans.run(keep.context, project_id, key, dupe.context);
204
+ deleteOrphanTrans.run(project_id, key, dupe.context);
205
+ deleteRow.run(dupe.id);
206
+ }
207
+ }
208
+ })();
209
+ console.log(`[db] Deduplication complete.`);
210
+ }
211
+ /**
212
+ * Ensure a default project exists. Returns the project row.
213
+ */
214
+ export function ensureDefaultProject() {
215
+ let project = rawDb.prepare('SELECT * FROM projects LIMIT 1').get();
216
+ if (!project) {
217
+ const api_key = randomUUID().replace(/-/g, '').slice(0, 24);
218
+ const info = rawDb.prepare('INSERT INTO projects (name, api_key) VALUES (?, ?)').run('default', api_key);
219
+ project = rawDb.prepare('SELECT * FROM projects WHERE id = ?').get(info.lastInsertRowid);
220
+ }
221
+ return project;
222
+ }
223
+ /**
224
+ * Reload project from DB (after updates).
225
+ */
226
+ export function reloadProject(projectId) {
227
+ return rawDb.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
228
+ }
229
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../server/db/seed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import './config.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC"}
@@ -0,0 +1,31 @@
1
+ import './config.js'; // Load .env first
2
+ import { PORT, BASE_PATH } from './config.js';
3
+ import { initializeDatabase, ensureDefaultProject } from './db/seed.js';
4
+ import { buildApp } from './app.js';
5
+ import { getDevHost } from './utils/network.js';
6
+ // ── Initialize database (tables, migrations, dedup)
7
+ initializeDatabase();
8
+ // ── Ensure default project exists
9
+ const project = ensureDefaultProject();
10
+ // ── Build & start Fastify app
11
+ const app = await buildApp(project);
12
+ app.listen({ port: PORT, host: '0.0.0.0' }, (err) => {
13
+ if (err) {
14
+ console.error(err);
15
+ process.exit(1);
16
+ }
17
+ const devHost = getDevHost();
18
+ const isDevServer = process.env.npm_lifecycle_event === 'dev' || process.argv[1]?.includes('nodemon') || process.argv[1]?.includes('tsx');
19
+ const host = process.env.PUBLIC_URL || (isDevServer
20
+ ? `http://${devHost}:6100${BASE_PATH}`
21
+ : `http://${devHost}:${PORT}${BASE_PATH}`);
22
+ console.log(`\n 🌐 Loco Translation Manager\n`);
23
+ console.log(` Dashboard : ${host}/dashboard/`);
24
+ console.log(` CDN script: ${host}/cdn/loco.js`);
25
+ console.log(` API docs : http://${devHost}:${PORT}/api/docs`);
26
+ console.log(` API key : ${project.api_key}`);
27
+ console.log(`\n Paste into any page:\n`);
28
+ console.log(` <script src="${host}/cdn/loco.js"></script>`);
29
+ console.log(` <script>Loco.init({ apiUrl: '${host}', apiKey: '${project.api_key}' });</script>\n`);
30
+ });
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC,CAAC,kBAAkB;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,qDAAqD;AACrD,kBAAkB,EAAE,CAAC;AAErB,mCAAmC;AACnC,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAC;AAEvC,+BAA+B;AAC/B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;AAEpC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;IAClD,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1I,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,WAAW;QACjD,CAAC,CAAC,UAAU,OAAO,QAAQ,SAAS,EAAE;QACtC,CAAC,CAAC,UAAU,OAAO,IAAI,IAAI,GAAG,SAAS,EAAE,CAAC,CAAC;IAE7C,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,aAAa,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,cAAc,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,IAAI,IAAI,WAAW,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,yBAAyB,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,eAAe,OAAO,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACtG,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export default function aiJobsRoutes(app: FastifyInstance, opts: {
3
+ project: any;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=ai-jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-jobs.d.ts","sourceRoot":"","sources":["../../server/routes/ai-jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,wBAA8B,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,iBAiJtF"}
@@ -0,0 +1,141 @@
1
+ import { rawDb } from '../db/index.js';
2
+ import { jobManager } from '../services/job-manager.js';
3
+ import { processTranslations } from '../services/ai-translation.js';
4
+ export default async function aiJobsRoutes(app, opts) {
5
+ app.get('/api/ai-jobs', {
6
+ schema: {
7
+ tags: ['AI Jobs'],
8
+ summary: 'List all AI translation jobs + stats',
9
+ response: {
10
+ 200: {
11
+ type: 'object',
12
+ properties: {
13
+ jobs: { type: 'array', items: { type: 'object', additionalProperties: true } },
14
+ stats: {
15
+ type: 'object',
16
+ properties: {
17
+ running: { type: 'number' },
18
+ pending: { type: 'number' },
19
+ total: { type: 'number' },
20
+ },
21
+ },
22
+ },
23
+ },
24
+ },
25
+ },
26
+ }, () => {
27
+ const jobs = jobManager.getAllJobs();
28
+ const stats = jobManager.getStats();
29
+ return { jobs, stats };
30
+ });
31
+ app.get('/api/ai-jobs/log', {
32
+ schema: {
33
+ tags: ['AI Jobs'],
34
+ summary: 'Get recent AI job history',
35
+ response: {
36
+ 200: {
37
+ type: 'array',
38
+ items: { type: 'object', additionalProperties: true },
39
+ },
40
+ },
41
+ },
42
+ }, () => {
43
+ const { project } = opts;
44
+ return rawDb.prepare('SELECT * FROM ai_job_log WHERE project_id = ? ORDER BY created_at DESC LIMIT 50').all(project.id);
45
+ });
46
+ app.post('/api/ai-jobs', {
47
+ schema: {
48
+ tags: ['AI Jobs'],
49
+ summary: 'Create a bulk AI translation job',
50
+ body: {
51
+ type: 'object',
52
+ properties: {
53
+ key_ids: { type: 'array', items: { type: 'number' } },
54
+ lang: { type: 'string' },
55
+ prompt_id: { type: 'string' },
56
+ include_screenshots: { type: 'boolean' },
57
+ mode: { type: 'string' },
58
+ },
59
+ required: ['key_ids', 'lang'],
60
+ },
61
+ response: {
62
+ 200: {
63
+ type: 'object',
64
+ properties: {
65
+ jobId: { type: 'string' },
66
+ total: { type: 'number' },
67
+ error: { type: 'string' },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ }, async (req) => {
73
+ const { project } = opts;
74
+ const { key_ids, lang, prompt_id, include_screenshots } = req.body;
75
+ if (!Array.isArray(key_ids) || key_ids.length === 0)
76
+ return { error: 'No items selected' };
77
+ const sanitizedIds = key_ids.map((id) => parseInt(id, 10)).filter((id) => Number.isFinite(id) && id > 0);
78
+ if (sanitizedIds.length === 0)
79
+ return { error: 'No valid item IDs provided' };
80
+ if (!lang)
81
+ return { error: 'lang is required' };
82
+ const promptTemplate = prompt_id
83
+ ? rawDb.prepare('SELECT * FROM prompt_templates WHERE id = ? AND project_id = ?').get(prompt_id, project.id)
84
+ : null;
85
+ if (!promptTemplate) {
86
+ return { error: 'Invalid prompt template' };
87
+ }
88
+ const placeholders = sanitizedIds.map(() => '?').join(',');
89
+ const items = rawDb.prepare(`SELECT id, key, context, url FROM textnodes WHERE id IN (${placeholders}) AND project_id = ?`).all(...sanitizedIds, project.id);
90
+ if (items.length === 0)
91
+ return { error: 'No matching textnodes found' };
92
+ const job = jobManager.createJob({
93
+ langCode: lang,
94
+ promptId: promptTemplate.id,
95
+ promptName: promptTemplate.name,
96
+ total: items.length,
97
+ });
98
+ rawDb.prepare('INSERT INTO ai_job_log (project_id, job_id, prompt_id, prompt_name, lang, total, status) VALUES (?, ?, ?, ?, ?, ?, ?)')
99
+ .run(project.id, job.id, promptTemplate.id, promptTemplate.name, lang, items.length, 'pending');
100
+ // Process in background (don't await)
101
+ processTranslations(job.id, items, lang, promptTemplate, opts.project.id, !!include_screenshots, opts.project.settings || '{}');
102
+ return { jobId: job.id, total: items.length };
103
+ });
104
+ app.get('/api/ai-jobs/:jobId', {
105
+ schema: {
106
+ tags: ['AI Jobs'],
107
+ summary: 'Get AI job status and progress',
108
+ params: {
109
+ type: 'object',
110
+ properties: { jobId: { type: 'string' } },
111
+ required: ['jobId'],
112
+ },
113
+ response: {
114
+ 200: { type: 'object', additionalProperties: true },
115
+ },
116
+ },
117
+ }, (req) => {
118
+ const job = jobManager.getJob(req.params.jobId);
119
+ if (!job)
120
+ return { error: 'Job not found' };
121
+ return job;
122
+ });
123
+ app.delete('/api/ai-jobs/:jobId', {
124
+ schema: {
125
+ tags: ['AI Jobs'],
126
+ summary: 'Cancel a running AI job',
127
+ params: {
128
+ type: 'object',
129
+ properties: { jobId: { type: 'string' } },
130
+ required: ['jobId'],
131
+ },
132
+ response: {
133
+ 200: { type: 'object', properties: { ok: { type: 'boolean' } } },
134
+ },
135
+ },
136
+ }, (req) => {
137
+ const cancelled = jobManager.cancelJob(req.params.jobId);
138
+ return { ok: cancelled };
139
+ });
140
+ }
141
+ //# sourceMappingURL=ai-jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-jobs.js","sourceRoot":"","sources":["../../server/routes/ai-jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export default function backupRoutes(app: FastifyInstance, opts: {
3
+ project: any;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=backup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../server/routes/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAkBxD,wBAA8B,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,iBAoHtF"}