localingos 0.1.36 → 0.1.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localingos",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "CLI tool to sync translations with Localingos",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/api.js CHANGED
@@ -6,7 +6,7 @@ export class LocalingosApi {
6
6
  this.apiKey = apiKey;
7
7
  }
8
8
 
9
- async sync(familyId, translatables, { batchSize = 100, onProgress } = {}) {
9
+ async sync(projectId, translatables, { batchSize = 100, onProgress } = {}) {
10
10
  // Push in batches to avoid API timeout
11
11
  const batches = [];
12
12
  for (let i = 0; i < translatables.length; i += batchSize) {
@@ -15,7 +15,7 @@ export class LocalingosApi {
15
15
 
16
16
  for (let i = 0; i < batches.length; i++) {
17
17
  await this._post('/translatable', {
18
- familyId,
18
+ projectId,
19
19
  translatableList: batches[i]
20
20
  });
21
21
  if (onProgress) onProgress(Math.min((i + 1) * batchSize, translatables.length), translatables.length, 'push');
@@ -27,7 +27,7 @@ export class LocalingosApi {
27
27
  const pullBatchSize = 50;
28
28
  for (let i = 0; i < ids.length; i += pullBatchSize) {
29
29
  const batch = ids.slice(i, i + pullBatchSize);
30
- const batchTranslations = await this._post(`/translation/${familyId}/batch`, { foreignIds: batch });
30
+ const batchTranslations = await this._post(`/translation/${projectId}/batch`, { foreignIds: batch });
31
31
  if (Array.isArray(batchTranslations)) allTranslations.push(...batchTranslations);
32
32
  if (onProgress) onProgress(Math.min(i + pullBatchSize, ids.length), ids.length, 'pull');
33
33
  }
@@ -38,23 +38,23 @@ export class LocalingosApi {
38
38
  return { translations: allTranslations, pending };
39
39
  }
40
40
 
41
- async getTranslatables(familyId) {
42
- return this._get(`/translatable/${familyId}`);
41
+ async getTranslatables(projectId) {
42
+ return this._get(`/translatable/${projectId}`);
43
43
  }
44
44
 
45
- async getTranslations(familyId) {
46
- return this._get(`/translation/${familyId}`);
45
+ async getTranslations(projectId) {
46
+ return this._get(`/translation/${projectId}`);
47
47
  }
48
48
 
49
- async postTranslatables(familyId, translatableList) {
49
+ async postTranslatables(projectId, translatableList) {
50
50
  return this._post('/translatable', {
51
- familyId,
51
+ projectId,
52
52
  translatableList
53
53
  });
54
54
  }
55
55
 
56
- async deleteTranslatables(familyId, foreignIds) {
57
- return this._request('DELETE', `/translatable/${familyId}`, { foreignIds });
56
+ async deleteTranslatables(projectId, foreignIds) {
57
+ return this._request('DELETE', `/translatable/${projectId}`, { foreignIds });
58
58
  }
59
59
 
60
60
  async _request(method, path, body) {
@@ -57,9 +57,9 @@ export async function initCommand() {
57
57
  },
58
58
  {
59
59
  type: 'input',
60
- name: 'familyId',
61
- message: 'Family ID (copy from Families page → Family ID column):',
62
- validate: (input) => input.trim().length > 0 || 'Family ID is required'
60
+ name: 'projectId',
61
+ message: 'Project ID (copy from Projects page → Project ID column):',
62
+ validate: (input) => input.trim().length > 0 || 'Project ID is required'
63
63
  },
64
64
  {
65
65
  type: 'input',
@@ -95,7 +95,7 @@ export async function initCommand() {
95
95
 
96
96
  const config = {
97
97
  apiKey: answers.apiKey.trim(),
98
- familyId: answers.familyId.trim(),
98
+ projectId: answers.projectId.trim(),
99
99
  sourceLocale: answers.sourceLocale.trim(),
100
100
  format: answers.format,
101
101
  sourceFile: answers.sourceFile.trim(),
@@ -89,7 +89,7 @@ export async function mcpServeCommand() {
89
89
  }
90
90
  const entries = readSourceEntries(config, cwd);
91
91
  if (!entries.length) return { content: [{ type: 'text', text: 'No source strings found.' }] };
92
- const result = await api('POST', '/sync', apiUrl, config.apiKey, { familyId: config.familyId, translatables: entries });
92
+ const result = await api('POST', '/sync', apiUrl, config.apiKey, { projectId: config.projectId, translatables: entries });
93
93
  const written = writeTranslations(result.translations || [], config, cwd);
94
94
  const pending = result.pending || [];
95
95
  const lines = [`Pushed ${entries.length} source strings`, `${(result.translations || []).length} translations available`,
@@ -102,18 +102,18 @@ export async function mcpServeCommand() {
102
102
  server.tool('localingos_push', 'Push source strings only.', {}, async () => {
103
103
  const entries = readSourceEntries(config, cwd);
104
104
  if (!entries.length) return { content: [{ type: 'text', text: 'No source strings found.' }] };
105
- await api('POST', '/translatable', apiUrl, config.apiKey, { familyId: config.familyId, translatableList: entries });
105
+ await api('POST', '/translatable', apiUrl, config.apiKey, { projectId: config.projectId, translatableList: entries });
106
106
  return { content: [{ type: 'text', text: `Pushed ${entries.length} source strings.` }] };
107
107
  });
108
108
 
109
109
  server.tool('localingos_pull', 'Pull latest translations and write locale files.', {}, async () => {
110
- const translations = await api('GET', `/translation/${config.familyId}`, apiUrl, config.apiKey);
110
+ const translations = await api('GET', `/translation/${config.projectId}`, apiUrl, config.apiKey);
111
111
  if (!translations?.length) return { content: [{ type: 'text', text: 'No translations available yet.' }] };
112
112
  const written = writeTranslations(translations, config, cwd);
113
113
  return { content: [{ type: 'text', text: `Pulled:\n${written.join('\n')}` }] };
114
114
  });
115
115
 
116
- server.tool('localingos_status', 'Show project status: family, locales, key counts.', {}, async () => {
116
+ server.tool('localingos_status', 'Show project status: project, locales, key counts.', {}, async () => {
117
117
  const entries = readSourceEntries(config, cwd);
118
118
  const outputDir = path.resolve(cwd, config.outputDir);
119
119
  let locales = [];
@@ -121,7 +121,7 @@ export async function mcpServeCommand() {
121
121
  const re = new RegExp('^' + (config.outputPattern || '{locale}.json').replace('{locale}', '(.+)').replace('.', '\\.') + '$');
122
122
  locales = fs.readdirSync(outputDir).map(f => f.match(re)?.[1]).filter(Boolean);
123
123
  }
124
- return { content: [{ type: 'text', text: `Family: ${config.familyId}\nSource: ${config.sourceLocale}\nKeys: ${entries.length}\nTranslations: ${locales.join(', ') || 'none'}` }] };
124
+ return { content: [{ type: 'text', text: `Project: ${config.projectId}\nSource: ${config.sourceLocale}\nKeys: ${entries.length}\nTranslations: ${locales.join(', ') || 'none'}` }] };
125
125
  });
126
126
 
127
127
  server.tool('localingos_search', 'Search translation keys or text.',
@@ -148,44 +148,44 @@ export async function mcpServeCommand() {
148
148
  }
149
149
  );
150
150
 
151
- // ── Family management tools ───────────────────────────────────────────
151
+ // ── Project management tools ───────────────────────────────────────────
152
152
 
153
- server.tool('localingos_family_members', 'List members of a family.',
154
- { familyId: z.string().describe('Family ID') },
155
- async ({ familyId }) => {
156
- const members = await api('GET', `/family/${familyId}/members`, apiUrl, config.apiKey);
153
+ server.tool('localingos_project_members', 'List members of a project.',
154
+ { projectId: z.string().describe('Project ID') },
155
+ async ({ projectId }) => {
156
+ const members = await api('GET', `/project/${projectId}/members`, apiUrl, config.apiKey);
157
157
  if (!members?.length) return { content: [{ type: 'text', text: 'No members found.' }] };
158
158
  const lines = members.map(m => `${m.userId} — ${m.authorization}`);
159
159
  return { content: [{ type: 'text', text: `${members.length} member(s):\n${lines.join('\n')}` }] };
160
160
  }
161
161
  );
162
162
 
163
- server.tool('localingos_add_member', 'Add a member to a family (admin only).',
164
- { familyId: z.string(), userId: z.string().describe('User ID to add'), role: z.enum(['ADMIN', 'WRITE', 'READ']).default('WRITE').describe('Role for the new member') },
165
- async ({ familyId, userId, role }) => {
163
+ server.tool('localingos_add_member', 'Add a member to a project (admin only).',
164
+ { projectId: z.string(), userId: z.string().describe('User ID to add'), role: z.enum(['ADMIN', 'WRITE', 'READ']).default('WRITE').describe('Role for the new member') },
165
+ async ({ projectId, userId, role }) => {
166
166
  try {
167
- await api('POST', `/family/${familyId}/members`, apiUrl, config.apiKey, { userId, authorization: role });
168
- return { content: [{ type: 'text', text: `Added ${userId} as ${role} to family ${familyId}` }] };
167
+ await api('POST', `/project/${projectId}/members`, apiUrl, config.apiKey, { userId, authorization: role });
168
+ return { content: [{ type: 'text', text: `Added ${userId} as ${role} to project ${projectId}` }] };
169
169
  } catch (e) { return { content: [{ type: 'text', text: `Failed: ${e.message}` }] }; }
170
170
  }
171
171
  );
172
172
 
173
- server.tool('localingos_remove_member', 'Remove a member from a family (admin only).',
174
- { familyId: z.string(), userId: z.string().describe('User ID to remove') },
175
- async ({ familyId, userId }) => {
173
+ server.tool('localingos_remove_member', 'Remove a member from a project (admin only).',
174
+ { projectId: z.string(), userId: z.string().describe('User ID to remove') },
175
+ async ({ projectId, userId }) => {
176
176
  try {
177
- await api('DELETE', `/family/${familyId}/members/${userId}`, apiUrl, config.apiKey);
178
- return { content: [{ type: 'text', text: `Removed ${userId} from family ${familyId}` }] };
177
+ await api('DELETE', `/project/${projectId}/members/${userId}`, apiUrl, config.apiKey);
178
+ return { content: [{ type: 'text', text: `Removed ${userId} from project ${projectId}` }] };
179
179
  } catch (e) { return { content: [{ type: 'text', text: `Failed: ${e.message}` }] }; }
180
180
  }
181
181
  );
182
182
 
183
- server.tool('localingos_billing_status', 'Show billing status for a family — plan, usage, owner.',
184
- { familyId: z.string().describe('Family ID') },
185
- async ({ familyId }) => {
183
+ server.tool('localingos_billing_status', 'Show billing status for a project — plan, usage, owner.',
184
+ { projectId: z.string().describe('Project ID') },
185
+ async ({ projectId }) => {
186
186
  try {
187
- const b = await api('GET', `/family/${familyId}/billing-status`, apiUrl, config.apiKey);
188
- if (!b.attached) return { content: [{ type: 'text', text: 'No billing attached to this family.' }] };
187
+ const b = await api('GET', `/project/${projectId}/billing-status`, apiUrl, config.apiKey);
188
+ if (!b.attached) return { content: [{ type: 'text', text: 'No billing attached to this project.' }] };
189
189
  const lines = [
190
190
  `Status: ${b.accessMode}${b.planName ? ' · ' + b.planName : ''}`,
191
191
  `Owner: ${b.ownerEmail || b.ownerUserId}`,
@@ -197,22 +197,22 @@ export async function mcpServeCommand() {
197
197
  }
198
198
  );
199
199
 
200
- server.tool('localingos_attach_billing', 'Attach your billing account to a family (admin only).',
201
- { familyId: z.string() },
202
- async ({ familyId }) => {
200
+ server.tool('localingos_attach_billing', 'Attach your billing account to a project (admin only).',
201
+ { projectId: z.string() },
202
+ async ({ projectId }) => {
203
203
  try {
204
- await api('POST', `/family/${familyId}/billing/attach`, apiUrl, config.apiKey);
205
- return { content: [{ type: 'text', text: `Billing attached to family ${familyId}` }] };
204
+ await api('POST', `/project/${projectId}/billing/attach`, apiUrl, config.apiKey);
205
+ return { content: [{ type: 'text', text: `Billing attached to project ${projectId}` }] };
206
206
  } catch (e) { return { content: [{ type: 'text', text: `Failed: ${e.message}` }] }; }
207
207
  }
208
208
  );
209
209
 
210
- server.tool('localingos_detach_billing', 'Detach billing from a family (admin only). Family becomes read-only.',
211
- { familyId: z.string() },
212
- async ({ familyId }) => {
210
+ server.tool('localingos_detach_billing', 'Detach billing from a project (admin only). Project becomes read-only.',
211
+ { projectId: z.string() },
212
+ async ({ projectId }) => {
213
213
  try {
214
- await api('POST', `/family/${familyId}/billing/detach`, apiUrl, config.apiKey);
215
- return { content: [{ type: 'text', text: `Billing detached from family ${familyId}` }] };
214
+ await api('POST', `/project/${projectId}/billing/detach`, apiUrl, config.apiKey);
215
+ return { content: [{ type: 'text', text: `Billing detached from project ${projectId}` }] };
216
216
  } catch (e) { return { content: [{ type: 'text', text: `Failed: ${e.message}` }] }; }
217
217
  }
218
218
  );
@@ -9,12 +9,12 @@ export async function pullCommand(options) {
9
9
  const apiUrl = resolveApiUrl(options.env);
10
10
  const formatter = getFormatter(config.format);
11
11
 
12
- console.log(chalk.blue(`⬇️ Pulling translations for family ${config.familyId}...`));
12
+ console.log(chalk.blue(`⬇️ Pulling translations for project ${config.projectId}...`));
13
13
  const api = new LocalingosApi(apiUrl, config.apiKey);
14
14
 
15
15
  let translations;
16
16
  try {
17
- translations = await api.getTranslations(config.familyId);
17
+ translations = await api.getTranslations(config.projectId);
18
18
  } catch (e) {
19
19
  console.error(chalk.red(`\nPull failed: ${e.message}`));
20
20
  process.exit(1);
@@ -56,11 +56,11 @@ export async function pushCommand(options) {
56
56
  }
57
57
 
58
58
  // 2. Push to API
59
- console.log(chalk.blue(`\n⬆️ Pushing ${translatables.length} translatables to family ${config.familyId}...`));
59
+ console.log(chalk.blue(`\n⬆️ Pushing ${translatables.length} translatables to project ${config.projectId}...`));
60
60
  const api = new LocalingosApi(apiUrl, config.apiKey);
61
61
 
62
62
  try {
63
- await api.postTranslatables(config.familyId, translatables);
63
+ await api.postTranslatables(config.projectId, translatables);
64
64
  } catch (e) {
65
65
  console.error(chalk.red(`\nPush failed: ${e.message}`));
66
66
  process.exit(1);
@@ -70,12 +70,12 @@ export async function syncCommand(options) {
70
70
  }
71
71
 
72
72
  // 2. Call /sync endpoint (push + pull in one call)
73
- console.log(chalk.blue(`\n🔄 Syncing with Localingos (family: ${config.familyId})...`));
73
+ console.log(chalk.blue(`\n🔄 Syncing with Localingos (project: ${config.projectId})...`));
74
74
  const api = new LocalingosApi(apiUrl, config.apiKey);
75
75
 
76
76
  let result;
77
77
  try {
78
- result = await api.sync(config.familyId, translatables, {
78
+ result = await api.sync(config.projectId, translatables, {
79
79
  onProgress: (done, total, phase) => {
80
80
  const label = phase === 'push' ? 'Pushed' : 'Pulled';
81
81
  process.stdout.write(`\r ${label} ${done}/${total} strings...`);
@@ -94,11 +94,11 @@ export async function syncCommand(options) {
94
94
  console.log(chalk.yellow(` ⏳ ${pending.length} keys pending translation: ${pending.slice(0, 5).join(', ')}${pending.length > 5 ? '...' : ''}`));
95
95
  }
96
96
 
97
- // 2b. Prune stale translatables from the remote family
97
+ // 2b. Prune stale translatables from the remote project
98
98
  if (options.prune) {
99
- console.log(chalk.blue('\n🔍 Checking for stale translatables in the remote family...'));
99
+ console.log(chalk.blue('\n🔍 Checking for stale translatables in the remote project...'));
100
100
  try {
101
- const remoteTranslatables = await api.getTranslatables(config.familyId);
101
+ const remoteTranslatables = await api.getTranslatables(config.projectId);
102
102
  const localIds = new Set(translatables.map(t => t.id));
103
103
  const staleIds = remoteTranslatables
104
104
  .filter(t => !localIds.has(t.foreignId || t.id))
@@ -111,7 +111,7 @@ export async function syncCommand(options) {
111
111
  }
112
112
  if (staleIds.length > 10) console.log(chalk.gray(` ... and ${staleIds.length - 10} more`));
113
113
 
114
- const deleteResult = await api.deleteTranslatables(config.familyId, staleIds);
114
+ const deleteResult = await api.deleteTranslatables(config.projectId, staleIds);
115
115
  console.log(chalk.green(` 🗑️ Deleted ${deleteResult.deleted} stale translatable(s) from remote`));
116
116
  } else {
117
117
  console.log(chalk.green(' ✅ No stale translatables found'));
package/src/config.js CHANGED
@@ -6,7 +6,7 @@ import chalk from 'chalk';
6
6
  * Config resolution order:
7
7
  *
8
8
  * 1. localingos.config.json — committed to repo, shared with team/CI
9
- * Contains: familyId, sourceLocale, format, sourceFile, outputDir, outputPattern, apiKeyEnv
9
+ * Contains: projectId, sourceLocale, format, sourceFile, outputDir, outputPattern, apiKeyEnv
10
10
  *
11
11
  * 2. .localingos.json — in .gitignore, local dev only
12
12
  * Contains: apiKey (and can override any field from the committed config)
@@ -90,7 +90,7 @@ function resolveApiKey(config) {
90
90
  config.apiKey = process.env[envVarName];
91
91
  }
92
92
 
93
- const required = ['apiKey', 'familyId', 'sourceLocale', 'format', 'sourceFile', 'outputDir'];
93
+ const required = ['apiKey', 'projectId', 'sourceLocale', 'format', 'sourceFile', 'outputDir'];
94
94
  const missing = required.filter(field => !config[field]);
95
95
  if (missing.length > 0) {
96
96
  if (missing.length === 1 && missing[0] === 'apiKey') {