@l10nmonster/helpers-java 3.0.0-alpha.9 → 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 CHANGED
@@ -20,7 +20,7 @@
20
20
  },
21
21
  {
22
22
  "path": "@semantic-release/npm",
23
- "npmPublish": true
23
+ "npmPublish": false
24
24
  },
25
25
  {
26
26
  "path": "@semantic-release/git",
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## @l10nmonster/helpers-java [3.0.1](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/helpers-java@3.0.0...@l10nmonster/helpers-java@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
@@ -1,10 +1,20 @@
1
1
  import { parseToEntries, stringifyFromEntries } from '@js.properties/properties';
2
2
 
3
3
  export default class JavaPropertiesFilter {
4
- async parseResource({ resource }) {
4
+ /**
5
+ * @param {Object} [params] - Configuration options
6
+ * @param {boolean} [params.enablePluralizationSuffixes=false] - Enable detection of plural forms via key suffixes (_one, _other, etc.)
7
+ */
8
+ constructor(params) {
9
+ this.enablePluralizationSuffixes = params?.enablePluralizationSuffixes || false;
10
+ }
11
+
12
+ async parseResource({ resource, sourcePluralForms, targetPluralForms }) {
5
13
  const parsedResource = parseToEntries(resource, { sep: true, eol: true, all: true, original: true, location: true });
6
14
  const segments = [];
7
15
  let previousComments = [];
16
+
17
+ // First pass: collect all segments
8
18
  for (const e of parsedResource) {
9
19
  if (e.key && e.sep.trim() === '=') {
10
20
  const location = {startLine: e.location.start.line, endLine: e.location.end.line}
@@ -30,24 +40,192 @@ export default class JavaPropertiesFilter {
30
40
  e.original.trim().length > 0 && previousComments.push(e.original);
31
41
  }
32
42
  }
43
+
44
+ // Second pass: detect and mark plural forms (only if enabled)
45
+ if (this.enablePluralizationSuffixes) {
46
+ const targetFormsSet = new Set(targetPluralForms);
47
+ const potentialPluralGroups = new Map(); // baseKey -> Map(suffix -> segment)
48
+
49
+ for (const seg of segments) {
50
+ const underscoreIdx = seg.sid.lastIndexOf('_');
51
+ if (underscoreIdx !== -1) {
52
+ const suffix = seg.sid.slice(underscoreIdx + 1);
53
+ if (targetFormsSet.has(suffix)) {
54
+ const baseKey = seg.sid.slice(0, underscoreIdx);
55
+ if (!potentialPluralGroups.has(baseKey)) {
56
+ potentialPluralGroups.set(baseKey, new Map());
57
+ }
58
+ potentialPluralGroups.get(baseKey).set(suffix, seg);
59
+ }
60
+ }
61
+ }
62
+
63
+ // For groups with all required source forms: set pluralForm and generate missing forms
64
+ // We need to reorder segments so all forms of a plural group are together in CLDR order
65
+ const pluralGroupsToReorder = new Map(); // baseKey -> forms Map
66
+
67
+ for (const [baseKey, forms] of potentialPluralGroups) {
68
+ const hasAllForms = sourcePluralForms.every(form => forms.has(form));
69
+ if (!hasAllForms) continue;
70
+
71
+ // Set pluralForm on existing segments
72
+ for (const [suffix, seg] of forms) {
73
+ seg.pluralForm = suffix;
74
+ }
75
+
76
+ // Generate missing forms from _other
77
+ const other = forms.get('other');
78
+ for (const suffix of targetPluralForms) {
79
+ if (!forms.has(suffix)) {
80
+ forms.set(suffix, {
81
+ sid: `${baseKey}_${suffix}`,
82
+ str: other.str,
83
+ pluralForm: suffix,
84
+ ...(other.notes && { notes: other.notes }),
85
+ ...(other.location && { location: other.location })
86
+ });
87
+ }
88
+ }
89
+
90
+ pluralGroupsToReorder.set(baseKey, forms);
91
+ }
92
+
93
+ // Rebuild segments with plural groups in correct order
94
+ if (pluralGroupsToReorder.size > 0) {
95
+ const newSegments = [];
96
+ const processedPluralKeys = new Set();
97
+
98
+ for (const seg of segments) {
99
+ const underscoreIdx = seg.sid.lastIndexOf('_');
100
+ const suffix = underscoreIdx !== -1 ? seg.sid.slice(underscoreIdx + 1) : null;
101
+ const baseKey = underscoreIdx !== -1 ? seg.sid.slice(0, underscoreIdx) : null;
102
+
103
+ if (baseKey && pluralGroupsToReorder.has(baseKey) && !processedPluralKeys.has(baseKey)) {
104
+ // Insert all forms of this plural group in CLDR order
105
+ processedPluralKeys.add(baseKey);
106
+ const forms = pluralGroupsToReorder.get(baseKey);
107
+ for (const form of targetPluralForms) {
108
+ if (forms.has(form)) {
109
+ newSegments.push(forms.get(form));
110
+ }
111
+ }
112
+ } else if (!baseKey || !pluralGroupsToReorder.has(baseKey)) {
113
+ // Non-plural segment
114
+ newSegments.push(seg);
115
+ }
116
+ // Skip plural segments that were already added via the group
117
+ }
118
+
119
+ return { segments: newSegments };
120
+ }
121
+ }
122
+
33
123
  return {
34
124
  segments,
35
125
  };
36
126
  }
37
127
 
38
- async translateResource({ resource, translator }) {
128
+ async translateResource({ resource, translator, sourcePluralForms, targetPluralForms }) {
39
129
  const parsedResource = parseToEntries(resource, { sep: true, eol: true, all: true, original: true });
40
130
  const translatedEntries = [];
41
- for (const entry of parsedResource) {
42
- if (entry.key) {
43
- const translation = await translator(entry.key, entry.element);
44
- if (translation !== undefined) {
45
- // eslint-disable-next-line no-unused-vars
46
- const { original, element, ...rest } = entry;
47
- translatedEntries.push({
48
- ...rest,
49
- element: translation,
50
- })
131
+
132
+ if (this.enablePluralizationSuffixes) {
133
+ const targetFormsSet = new Set(targetPluralForms);
134
+
135
+ // First pass: identify plural groups and collect entries
136
+ const potentialPluralGroups = new Map(); // baseKey -> Map(suffix -> entry)
137
+
138
+ for (const entry of parsedResource) {
139
+ if (entry.key) {
140
+ const underscoreIdx = entry.key.lastIndexOf('_');
141
+ if (underscoreIdx !== -1) {
142
+ const suffix = entry.key.slice(underscoreIdx + 1);
143
+ if (targetFormsSet.has(suffix)) {
144
+ const baseKey = entry.key.slice(0, underscoreIdx);
145
+ if (!potentialPluralGroups.has(baseKey)) {
146
+ potentialPluralGroups.set(baseKey, new Map());
147
+ }
148
+ potentialPluralGroups.get(baseKey).set(suffix, entry);
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Identify valid plural groups (those with all required source forms)
155
+ const validPluralBaseKeys = new Set();
156
+ for (const [baseKey, forms] of potentialPluralGroups) {
157
+ const hasAllForms = sourcePluralForms.every(form => forms.has(form));
158
+ if (hasAllForms) {
159
+ validPluralBaseKeys.add(baseKey);
160
+ }
161
+ }
162
+
163
+ // Second pass: translate entries and handle plurals
164
+ const processedPluralGroups = new Set();
165
+
166
+ for (const entry of parsedResource) {
167
+ if (entry.key) {
168
+ const underscoreIdx = entry.key.lastIndexOf('_');
169
+ const suffix = underscoreIdx !== -1 ? entry.key.slice(underscoreIdx + 1) : null;
170
+ const baseKey = underscoreIdx !== -1 ? entry.key.slice(0, underscoreIdx) : null;
171
+
172
+ // Check if this is part of a valid plural group
173
+ if (baseKey && validPluralBaseKeys.has(baseKey) && targetFormsSet.has(suffix)) {
174
+ // Skip if we already processed this plural group
175
+ if (processedPluralGroups.has(baseKey)) {
176
+ continue;
177
+ }
178
+ processedPluralGroups.add(baseKey);
179
+
180
+ // Get the _other form as template for generating missing forms
181
+ const otherEntry = potentialPluralGroups.get(baseKey).get('other');
182
+ const templateEntry = otherEntry || entry;
183
+
184
+ // Generate all required target forms
185
+ for (const targetSuffix of targetPluralForms) {
186
+ const targetKey = `${baseKey}_${targetSuffix}`;
187
+ const sourceEntry = potentialPluralGroups.get(baseKey).get(targetSuffix) || otherEntry;
188
+
189
+ if (sourceEntry) {
190
+ const translation = await translator(targetKey, sourceEntry.element);
191
+ if (translation !== undefined) {
192
+ // eslint-disable-next-line no-unused-vars
193
+ const { original, element, key, ...rest } = templateEntry;
194
+ translatedEntries.push({
195
+ ...rest,
196
+ key: targetKey,
197
+ element: translation,
198
+ });
199
+ }
200
+ }
201
+ }
202
+ } else {
203
+ // Regular (non-plural) entry
204
+ const translation = await translator(entry.key, entry.element);
205
+ if (translation !== undefined) {
206
+ // eslint-disable-next-line no-unused-vars
207
+ const { original, element, ...rest } = entry;
208
+ translatedEntries.push({
209
+ ...rest,
210
+ element: translation,
211
+ });
212
+ }
213
+ }
214
+ }
215
+ }
216
+ } else {
217
+ // Simple translation without plural handling
218
+ for (const entry of parsedResource) {
219
+ if (entry.key) {
220
+ const translation = await translator(entry.key, entry.element);
221
+ if (translation !== undefined) {
222
+ // eslint-disable-next-line no-unused-vars
223
+ const { original, element, ...rest } = entry;
224
+ translatedEntries.push({
225
+ ...rest,
226
+ element: translation,
227
+ });
228
+ }
51
229
  }
52
230
  }
53
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l10nmonster/helpers-java",
3
- "version": "3.0.0-alpha.9",
3
+ "version": "3.0.1",
4
4
  "description": "Helpers to deal with Java file formats",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,6 +13,6 @@
13
13
  "@js.properties/properties": "^0.5.4"
14
14
  },
15
15
  "peerDependencies": {
16
- "@l10nmonster/core": "^3.0.0-alpha.0"
16
+ "@l10nmonster/core": "3.1.0"
17
17
  }
18
18
  }