Skip to content

Commit 10252ce

Browse files
committed
feat: add android support
1 parent fbce978 commit 10252ce

File tree

5 files changed

+103
-2
lines changed

5 files changed

+103
-2
lines changed

.changeset/soft-cheetahs-wink.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@replexica/spec": minor
3+
"@replexica/cli": minor
4+
---
5+
6+
Add android support

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"slugify": "^1.6.6",
3535
"typescript": "^5.4.5",
3636
"vitest": "^2.0.0",
37+
"xml2js": "^0.6.2",
3738
"yaml": "^2.4.5",
3839
"zod": "^3.23.8"
3940
},
@@ -44,6 +45,7 @@
4445
"@types/lodash": "^4.17.5",
4546
"@types/node": "^20.14.10",
4647
"@types/object-hash": "^3.0.6",
48+
"@types/xml2js": "^0.4.14",
4749
"tsup": "^8.1.0"
4850
}
4951
}

packages/cli/src/workers/bucket/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { allLocalesSchema, bucketTypeSchema } from '@replexica/spec';
22
import Z from 'zod';
33
import { createFileLoader, IBucketLoader } from './loader';
4-
import { createJsonParser, createMarkdownParser, createXcodeParser, createYamlParser, IBucketParser } from './parser';
4+
import { createAndroidParser, createJsonParser, createMarkdownParser, createXcodeParser, createYamlParser, IBucketParser } from './parser';
55
import { BucketPathResolver, patternPathResolver, plainPathResolver } from './path-resolver';
66

77
export function createBucketProcessor(bucketType: Z.infer<typeof bucketTypeSchema>, bucketPath: string) {
@@ -38,6 +38,12 @@ export function createBucketProcessor(bucketType: Z.infer<typeof bucketTypeSchem
3838
parser: createXcodeParser(),
3939
pathResolver: plainPathResolver,
4040
});
41+
case 'android':
42+
return composeBucketProcessor(bucketPath, {
43+
storage: createFileLoader(),
44+
parser: createAndroidParser(),
45+
pathResolver: patternPathResolver,
46+
});
4147
case 'compiler':
4248
return composeBucketProcessor(`node_modules/@replexica/.cache/[locale].json`, {
4349
storage: createFileLoader(),

packages/cli/src/workers/bucket/parser.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MD5 } from 'object-hash';
1+
import { parseStringPromise, Builder } from 'xml2js';
22
import YAML from 'yaml';
33
import _ from 'lodash';
44

@@ -163,4 +163,90 @@ export function createXcodeParser() {
163163
return JSON.stringify(resultData, null, 2);
164164
}
165165
};
166+
}
167+
168+
export function createAndroidParser(): IBucketParser {
169+
return {
170+
async deserialize(locale: string, content: any) {
171+
const parsedResult = await parseStringPromise(content);
172+
const resources = parsedResult.resources;
173+
174+
const result: Record<string, any> = {};
175+
176+
// Parse single strings
177+
if (resources.string) {
178+
resources.string.forEach((item: any) => {
179+
result[item.$.name] = item._ || '';
180+
});
181+
}
182+
183+
// Parse string arrays
184+
if (resources['string-array']) {
185+
resources['string-array'].forEach((item: any) => {
186+
result[item.$.name] = item.item.map((i: any) => i._ || '');
187+
});
188+
}
189+
190+
// Parse plurals
191+
if (resources.plurals) {
192+
resources.plurals.forEach((item: any) => {
193+
const plurals: Record<string, string> = {};
194+
item.item.forEach((i: any) => {
195+
plurals[i.$.quantity] = i._ || '';
196+
});
197+
result[item.$.name] = plurals;
198+
});
199+
}
200+
201+
return result;
202+
},
203+
204+
async serialize(locale: string, content: Record<string, any>) {
205+
const builder = new Builder({ headless: true })
206+
const resources: any = {
207+
resources: {
208+
string: [],
209+
'string-array': [],
210+
plurals: [],
211+
},
212+
};
213+
214+
for (const key in content) {
215+
if (typeof content[key] === 'string') {
216+
resources.resources.string.push({ $: { name: key }, _: content[key] });
217+
} else if (Array.isArray(content[key])) {
218+
resources.resources['string-array'].push({
219+
$: { name: key },
220+
item: content[key].map((value: string) => ({ _: value })),
221+
});
222+
} else if (typeof content[key] === 'object') {
223+
const pluralItems = [];
224+
for (const quantity in content[key]) {
225+
pluralItems.push({
226+
$: { quantity },
227+
_: content[key][quantity],
228+
});
229+
}
230+
resources.resources.plurals.push({
231+
$: { name: key },
232+
item: pluralItems,
233+
});
234+
}
235+
}
236+
237+
// Remove empty sections if they have no items
238+
if (resources.resources.string.length === 0) {
239+
delete resources.resources.string;
240+
}
241+
if (resources.resources['string-array'].length === 0) {
242+
delete resources.resources['string-array'];
243+
}
244+
if (resources.resources.plurals.length === 0) {
245+
delete resources.resources.plurals;
246+
}
247+
248+
const xml = builder.buildObject(resources);
249+
return xml;
250+
},
251+
};
166252
}

packages/spec/src/formats.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const bucketTypes = [
77
'yaml',
88
'yaml-root-key',
99
'xcode',
10+
'android',
1011
] as const;
1112

1213
export const bucketTypeSchema = Z.enum(bucketTypes);

0 commit comments

Comments
 (0)