@l10nmonster/helpers-java 3.0.0-alpha.15 → 3.0.0-alpha.17

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