Skip to content

Commit 8007a26

Browse files
committed
feat(cli): add i18n command implementation
1 parent c4a15a3 commit 8007a26

File tree

4 files changed

+143
-42
lines changed

4 files changed

+143
-42
lines changed

packages/cli/src/auth.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/cli/src/i18n.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { Command } from 'commander';
2+
import Ora from 'ora';
3+
import { getEnv } from './services/env.js';
4+
import path from 'path';
5+
import fs from 'fs/promises';
6+
7+
const buildDataDir = path.resolve(process.cwd(), 'node_modules', '@replexica/translations');
8+
const buildDataFilePath = path.resolve(buildDataDir, '.replexica.json');
9+
const spinner = Ora();
10+
11+
export default new Command()
12+
.command('i18n')
13+
.description('Process i18n with Replexica')
14+
.helpOption('-h, --help', 'Show help')
15+
.action(async () => {
16+
const env = getEnv();
17+
spinner.start('Loading Replexica build data...');
18+
const buildData = await loadBuildData();
19+
if (!buildData) {
20+
spinner.fail(`Couldn't load Replexica build data. Did you forget to run 'replexica i18n'?`);
21+
return process.exit(1);
22+
}
23+
24+
const localeSource = buildData.settings?.locale?.source;
25+
if (!localeSource) {
26+
spinner.fail(`No source locale found in Replexica build data. Please check your Replexica configuration and run 'replexica i18n' again.`);
27+
return process.exit(1);
28+
}
29+
30+
const localeTargets = buildData.settings?.locale?.targets || [];
31+
if (!localeTargets.length) {
32+
spinner.fail(`No target locales found in Replexica build data. Please check your Replexica configuration and run 'replexica i18n' again.`);
33+
return process.exit(1);
34+
}
35+
36+
const localeSourceData = await loadLocaleData(localeSource);
37+
if (!localeSourceData) {
38+
spinner.fail(`Couldn't load source locale data for source locale ${localeSource}. Did you forget to run 'replexica i18n'?`);
39+
return process.exit(1);
40+
}
41+
42+
for (const target of localeTargets) {
43+
spinner.start(`Processing i18n for ${target}...`);
44+
const result = await processI18n(
45+
{ source: localeSource, target },
46+
buildData.meta,
47+
localeSourceData,
48+
);
49+
spinner.succeed(`i18n processed for ${target}!`);
50+
51+
spinner.start(`Saving full i18n data for ${target}...`);
52+
await saveFullLocaleData(target, result.data);
53+
spinner.succeed(`Full i18n data saved for ${target}!`);
54+
55+
spinner.start(`Saving client i18n data for ${target}...`);
56+
await saveClientLocaleData(target, result.data, buildData.meta);
57+
spinner.succeed(`Client i18n data saved for ${target}!`);
58+
}
59+
60+
spinner.succeed('All i18n processed!');
61+
});
62+
63+
async function processI18n(
64+
locale: { source: string, target: string },
65+
meta: any,
66+
data: any,
67+
) {
68+
const env = getEnv();
69+
70+
const res = await fetch(`${env.REPLEXICA_API_URL}/i18n`, {
71+
method: 'POST',
72+
headers: {
73+
'Content-Type': 'application/json',
74+
Authorization: `Bearer ${env.REPLEXICA_API_KEY}`,
75+
},
76+
body: JSON.stringify({
77+
locale,
78+
meta,
79+
data,
80+
}),
81+
});
82+
if (!res.ok) {
83+
const errorText = await res.text();
84+
throw new Error(`Failed to process i18n: ${errorText}`);
85+
}
86+
const payload = await res.json();
87+
return payload;
88+
}
89+
90+
async function loadBuildData() {
91+
const fileExists = await fs.access(
92+
buildDataFilePath,
93+
fs.constants.F_OK,
94+
).then(() => true).catch(() => false);
95+
if (!fileExists) { return null; }
96+
97+
const buildDataFile = await fs.readFile(buildDataFilePath, 'utf-8');
98+
const buildData = JSON.parse(buildDataFile);
99+
return buildData;
100+
}
101+
102+
async function loadLocaleData(locale: string) {
103+
const localeFilePath = path.resolve(buildDataDir, `${locale}.json`);
104+
const fileExists = await fs.access(
105+
localeFilePath,
106+
fs.constants.F_OK,
107+
).then(() => true).catch(() => false);
108+
if (!fileExists) { return null; }
109+
110+
const localeFile = await fs.readFile(localeFilePath, 'utf-8');
111+
const localeData = JSON.parse(localeFile);
112+
return localeData;
113+
}
114+
115+
async function saveFullLocaleData(locale: string, data: any) {
116+
const localeFilePath = path.resolve(buildDataDir, `${locale}.json`);
117+
spinner.succeed(`Saved full ${locale} locale data to ${localeFilePath}`);
118+
await fs.writeFile(localeFilePath, JSON.stringify(data, null, 2));
119+
}
120+
121+
async function saveClientLocaleData(locale: string, data: any, meta: any) {
122+
const newData = {
123+
...data,
124+
};
125+
126+
for (const [fileId, fileData] of Object.entries(meta.files || {})) {
127+
const isClient = (fileData as any).isClient;
128+
129+
if (!isClient) {
130+
delete newData[fileId];
131+
}
132+
}
133+
134+
const localeFilePath = path.resolve(buildDataDir, `${locale}.client.json`);
135+
spinner.succeed(`Saved client ${locale} locale data to ${localeFilePath}`);
136+
await fs.writeFile(localeFilePath, JSON.stringify(newData, null, 2));
137+
}

packages/cli/src/index.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1+
import dotenv from 'dotenv';
12
import { Command } from 'commander';
2-
import Ora from 'ora';
3-
import { setTimeout } from 'timers/promises';
43

5-
import authCmd from './auth.js';
6-
import translateCmd from './translate.js';
4+
import i18nCmd from './i18n.js';
5+
6+
dotenv.config();
77

88
export default new Command()
99
.name('replexica')
1010
.description('Replexica CLI')
1111
.helpOption('-h, --help', 'Show help')
12-
.addCommand(authCmd)
13-
.addCommand(translateCmd)
14-
.action(async (options) => {
15-
const spinner = Ora();
16-
spinner.start('Loading...');
17-
await setTimeout(2000);
18-
spinner.succeed('Welcome to Replexica CLI!');
19-
})
20-
.parse(process.argv);
12+
.addCommand(i18nCmd)
13+
.parse(process.argv);

packages/cli/src/translate.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)