@l10nmonster/helpers-android 3.0.0-alpha.8 → 3.0.1
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/.releaserc.json +1 -1
- package/CHANGELOG.md +9 -0
- package/filter.js +73 -19
- package/package.json +2 -2
package/.releaserc.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## @l10nmonster/helpers-android [3.0.1](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/helpers-android@3.0.0...@l10nmonster/helpers-android@3.0.1) (2025-12-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Add proper pluralization expansion support ([3d062bb](https://public-github/l10nmonster/l10nmonster/commit/3d062bbb3272c61e969b419a56a7a5e347ab96c6))
|
|
7
|
+
* Pluralization improvements ([5964250](https://public-github/l10nmonster/l10nmonster/commit/596425092c425cc8d6c312ef58509c4c3c537431))
|
|
8
|
+
* **server:** Fix cart cleanup ([9bbcab9](https://public-github/l10nmonster/l10nmonster/commit/9bbcab93e1fd20aeb09f59c828665159f091f37c))
|
|
9
|
+
|
|
1
10
|
# Changelog
|
|
2
11
|
|
|
3
12
|
All notable changes to this project will be documented in this file.
|
package/filter.js
CHANGED
|
@@ -35,9 +35,10 @@ export class AndroidXMLFilter {
|
|
|
35
35
|
* Parse an Android resource file and extract translatable segments.
|
|
36
36
|
* @param {Object} params - Parameters for parsing the resource.
|
|
37
37
|
* @param {string} params.resource - The XML content of the Android resource file.
|
|
38
|
+
* @param {string[]} [params.targetPluralForms] - Array of plural forms required for target languages.
|
|
38
39
|
* @returns {Promise<Object>} An object containing the extracted segments.
|
|
39
40
|
*/
|
|
40
|
-
async parseResource({ resource }) {
|
|
41
|
+
async parseResource({ resource, targetPluralForms }) {
|
|
41
42
|
const segments = [];
|
|
42
43
|
const parsingOptions = {
|
|
43
44
|
ignoreAttributes: false,
|
|
@@ -52,6 +53,7 @@ export class AndroidXMLFilter {
|
|
|
52
53
|
parseTagValue: false,
|
|
53
54
|
trimValues: true,
|
|
54
55
|
};
|
|
56
|
+
|
|
55
57
|
const parser = new XMLParser(parsingOptions);
|
|
56
58
|
for (const rootNode of parser.parse(resource)) {
|
|
57
59
|
if ('resources' in rootNode) {
|
|
@@ -71,17 +73,42 @@ export class AndroidXMLFilter {
|
|
|
71
73
|
lastComment = null;
|
|
72
74
|
}
|
|
73
75
|
} else if ('plurals' in resNode) { // TODO: support string-array
|
|
76
|
+
const pluralName = resNode[':@'].name;
|
|
77
|
+
const pluralForms = new Map(); // quantity -> { seg, notes }
|
|
78
|
+
let pluralComment = lastComment;
|
|
79
|
+
|
|
80
|
+
// Collect existing plural forms
|
|
74
81
|
for (const itemNode of resNode.plurals) {
|
|
75
82
|
if ('#comment' in itemNode) {
|
|
76
|
-
|
|
83
|
+
pluralComment = itemNode['#comment'].map(e => e['#text']).join('').trim();
|
|
77
84
|
} else if ('item' in itemNode) {
|
|
85
|
+
const quantity = itemNode[':@'].quantity;
|
|
78
86
|
const seg = {
|
|
79
|
-
sid: `${
|
|
80
|
-
|
|
87
|
+
sid: `${pluralName}_${quantity}`,
|
|
88
|
+
pluralForm: quantity,
|
|
81
89
|
str: collapseTextNodes(itemNode.item)
|
|
82
90
|
};
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
pluralComment && (seg.notes = pluralComment);
|
|
92
|
+
pluralForms.set(quantity, seg);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Android <plurals> element explicitly defines plural rules
|
|
97
|
+
// Expansion can happen as long as 'other' form is present
|
|
98
|
+
const otherForm = pluralForms.get('other');
|
|
99
|
+
|
|
100
|
+
// Add forms in natural plural order (existing or generated from 'other')
|
|
101
|
+
for (const form of targetPluralForms) {
|
|
102
|
+
if (pluralForms.has(form)) {
|
|
103
|
+
segments.push(pluralForms.get(form));
|
|
104
|
+
} else if (otherForm) {
|
|
105
|
+
// Generate missing form from 'other'
|
|
106
|
+
segments.push({
|
|
107
|
+
sid: `${pluralName}_${form}`,
|
|
108
|
+
pluralForm: form,
|
|
109
|
+
str: otherForm.str,
|
|
110
|
+
...(otherForm.notes && { notes: otherForm.notes })
|
|
111
|
+
});
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
114
|
lastComment = null;
|
|
@@ -102,9 +129,10 @@ export class AndroidXMLFilter {
|
|
|
102
129
|
* @param {Object} params - Parameters for translating the resource.
|
|
103
130
|
* @param {string} params.resource - The XML content of the Android resource file.
|
|
104
131
|
* @param {Function} params.translator - A function that translates a string given its ID and source text.
|
|
132
|
+
* @param {string[]} [params.targetPluralForms] - Array of plural forms required for the target language.
|
|
105
133
|
* @returns {Promise<string|null>} The translated XML content, or null if no translations were made.
|
|
106
134
|
*/
|
|
107
|
-
async translateResource({ resource, translator }) {
|
|
135
|
+
async translateResource({ resource, translator, targetPluralForms }) {
|
|
108
136
|
const parsingOptions = {
|
|
109
137
|
ignoreAttributes: false,
|
|
110
138
|
processEntities: true,
|
|
@@ -118,6 +146,7 @@ export class AndroidXMLFilter {
|
|
|
118
146
|
parseTagValue: false,
|
|
119
147
|
trimValues: true,
|
|
120
148
|
};
|
|
149
|
+
|
|
121
150
|
const parser = new XMLParser(parsingOptions);
|
|
122
151
|
const parsedResource = parser.parse(resource);
|
|
123
152
|
const nodesToDelete = [];
|
|
@@ -138,26 +167,51 @@ export class AndroidXMLFilter {
|
|
|
138
167
|
} else {
|
|
139
168
|
nodesToDelete.push(resNode);
|
|
140
169
|
}
|
|
141
|
-
} else if ('plurals' in resNode) {
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
} else if ('plurals' in resNode) {
|
|
171
|
+
const pluralName = resNode[':@'].name;
|
|
172
|
+
|
|
173
|
+
// Collect source plural forms
|
|
174
|
+
const sourceForms = new Map(); // quantity -> { text, itemNode }
|
|
144
175
|
for (const itemNode of resNode.plurals) {
|
|
145
|
-
if ('
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
if ('item' in itemNode) {
|
|
177
|
+
sourceForms.set(itemNode[':@'].quantity, {
|
|
178
|
+
text: collapseTextNodes(itemNode.item),
|
|
179
|
+
itemNode
|
|
180
|
+
});
|
|
149
181
|
}
|
|
150
|
-
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Get 'other' form for generating missing target forms
|
|
185
|
+
// Android <plurals> is explicitly a plural, so we can expand as long as 'other' exists
|
|
186
|
+
const otherForm = sourceForms.get('other');
|
|
187
|
+
|
|
188
|
+
// Build new plurals node with only required target forms in CLDR order
|
|
189
|
+
const newPluralItems = [];
|
|
190
|
+
let dropPlural = false;
|
|
191
|
+
|
|
192
|
+
for (const form of targetPluralForms) {
|
|
193
|
+
const sourceForm = sourceForms.get(form) ?? otherForm;
|
|
194
|
+
if (!sourceForm) {
|
|
195
|
+
// Can't generate this required form - no source and no fallback
|
|
196
|
+
dropPlural = true;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const translation = await translator(`${pluralName}_${form}`, sourceForm.text);
|
|
151
200
|
if (translation === undefined) {
|
|
152
201
|
dropPlural = true;
|
|
153
|
-
|
|
154
|
-
itemNode.item = [ { '#text': translation } ];
|
|
202
|
+
break;
|
|
155
203
|
}
|
|
204
|
+
newPluralItems.push({
|
|
205
|
+
item: [{ '#text': translation }],
|
|
206
|
+
':@': { quantity: form }
|
|
207
|
+
});
|
|
156
208
|
}
|
|
157
|
-
|
|
158
|
-
if (dropPlural) {
|
|
209
|
+
|
|
210
|
+
if (dropPlural || newPluralItems.length === 0) {
|
|
159
211
|
nodesToDelete.push(resNode);
|
|
160
212
|
} else {
|
|
213
|
+
// Replace plurals with new items containing only target forms
|
|
214
|
+
resNode.plurals = newPluralItems;
|
|
161
215
|
translated++;
|
|
162
216
|
}
|
|
163
217
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@l10nmonster/helpers-android",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Helpers to deal with Android file formats",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -14,6 +14,6 @@
|
|
|
14
14
|
"xml-formatter": "^3"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@l10nmonster/core": "
|
|
17
|
+
"@l10nmonster/core": "3.1.0"
|
|
18
18
|
}
|
|
19
19
|
}
|