@l10nmonster/helpers-ios 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 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,11 @@
1
+ ## @l10nmonster/helpers-ios [3.0.1](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/helpers-ios@3.0.0...@l10nmonster/helpers-ios@3.0.1) (2025-12-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Pluralization improvements ([5964250](https://public-github/l10nmonster/l10nmonster/commit/596425092c425cc8d6c312ef58509c4c3c537431))
7
+ * **server:** Fix cart cleanup ([9bbcab9](https://public-github/l10nmonster/l10nmonster/commit/9bbcab93e1fd20aeb09f59c828665159f091f37c))
8
+
1
9
  # Changelog
2
10
 
3
11
  All notable changes to this project will be documented in this file.
package/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import i18nStringsFiles from '@l10nmonster/i18n-strings-files';
2
2
  import { regex } from '@l10nmonster/core';
3
3
 
4
+ export { StringsdictFilter } from './stringsdict.js';
5
+
4
6
  // filter for iOS .strings file (in utf-8)
5
7
  export class StringsFilter {
6
8
  constructor(params) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l10nmonster/helpers-ios",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.1",
4
4
  "description": "Helpers to deal with iOS file formats",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -10,9 +10,10 @@
10
10
  "author": "Diego Lagunas",
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
- "@l10nmonster/i18n-strings-files": "^3.0.0"
13
+ "@l10nmonster/i18n-strings-files": "^3.0.0",
14
+ "plist": "^3.1.0"
14
15
  },
15
16
  "peerDependencies": {
16
- "@l10nmonster/core": "^3.0.0-alpha.0"
17
+ "@l10nmonster/core": "3.1.0"
17
18
  }
18
19
  }
package/stringsdict.js ADDED
@@ -0,0 +1,148 @@
1
+ import plist from 'plist';
2
+
3
+ /**
4
+ * Filter for iOS .stringsdict files (plist format for pluralization).
5
+ * @see https://developer.apple.com/documentation/xcode/localizing-strings-that-contain-plurals
6
+ */
7
+ export class StringsdictFilter {
8
+
9
+ /**
10
+ * Create a StringsdictFilter.
11
+ * @param {Object} [params] - Configuration options for the filter.
12
+ */
13
+ constructor(params) {
14
+ this.emitComments = params?.emitComments || false;
15
+ }
16
+
17
+ /**
18
+ * Parse a stringsdict file and extract translatable segments.
19
+ * @param {Object} params - Parameters for parsing the resource.
20
+ * @param {string} params.resource - The XML/plist content of the stringsdict file.
21
+ * @param {string[]} [params.sourcePluralForms] - Array of plural forms required in source language.
22
+ * @param {string[]} [params.targetPluralForms] - Array of plural forms required for target languages.
23
+ * @returns {Promise<Object>} An object containing the extracted segments.
24
+ */
25
+ async parseResource({ resource, sourcePluralForms, targetPluralForms }) {
26
+ const segments = [];
27
+ const parsedPlist = plist.parse(resource);
28
+
29
+ for (const [key, value] of Object.entries(parsedPlist)) {
30
+ if (typeof value !== 'object' || !value.NSStringLocalizedFormatKey) {
31
+ continue;
32
+ }
33
+
34
+ const formatKey = value.NSStringLocalizedFormatKey;
35
+ // Extract variable names from format key (e.g., "%#@items@" -> "items")
36
+ const variableMatches = formatKey.matchAll(/%#@(\w+)@/g);
37
+
38
+ for (const match of variableMatches) {
39
+ const varName = match[1];
40
+ const varConfig = value[varName];
41
+
42
+ if (!varConfig ||
43
+ varConfig.NSStringFormatSpecTypeKey !== 'NSStringPluralRuleType') {
44
+ continue;
45
+ }
46
+
47
+ // stringsdict NSStringPluralRuleType explicitly defines plural rules
48
+ // Expansion can happen as long as 'other' form is present
49
+ const otherStr = varConfig.other;
50
+
51
+ // Add forms in natural plural order (existing or generated from 'other')
52
+ for (const form of targetPluralForms) {
53
+ const str = varConfig[form] ?? otherStr;
54
+ if (str !== undefined) {
55
+ segments.push({
56
+ sid: `${key}/${varName}_${form}`,
57
+ pluralForm: form,
58
+ str,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ return { segments };
66
+ }
67
+
68
+ /**
69
+ * Translate a stringsdict file using the provided translator function.
70
+ * @param {Object} params - Parameters for translating the resource.
71
+ * @param {string} params.resource - The XML/plist content of the stringsdict file.
72
+ * @param {Function} params.translator - A function that translates a string given its ID and source text.
73
+ * @param {string[]} [params.sourcePluralForms] - Array of plural forms in the source language.
74
+ * @param {string[]} [params.targetPluralForms] - Array of plural forms required for the target language.
75
+ * @returns {Promise<string|null>} The translated stringsdict content, or null if no translations were made.
76
+ */
77
+ async translateResource({ resource, translator, sourcePluralForms, targetPluralForms }) {
78
+ const parsedPlist = plist.parse(resource);
79
+
80
+ const translatedPlist = {};
81
+ let translated = 0;
82
+
83
+ for (const [key, value] of Object.entries(parsedPlist)) {
84
+ if (typeof value !== 'object' || !value.NSStringLocalizedFormatKey) {
85
+ continue;
86
+ }
87
+
88
+ const formatKey = value.NSStringLocalizedFormatKey;
89
+ const variableMatches = [...formatKey.matchAll(/%#@(\w+)@/g)];
90
+
91
+ const translatedEntry = {
92
+ NSStringLocalizedFormatKey: formatKey,
93
+ };
94
+
95
+ let entryHasTranslations = true;
96
+
97
+ for (const match of variableMatches) {
98
+ const varName = match[1];
99
+ const varConfig = value[varName];
100
+
101
+ if (!varConfig ||
102
+ varConfig.NSStringFormatSpecTypeKey !== 'NSStringPluralRuleType') {
103
+ continue;
104
+ }
105
+
106
+ const translatedVarConfig = {
107
+ NSStringFormatSpecTypeKey: varConfig.NSStringFormatSpecTypeKey,
108
+ NSStringFormatValueTypeKey: varConfig.NSStringFormatValueTypeKey || 'd',
109
+ };
110
+
111
+ // stringsdict NSStringPluralRuleType explicitly defines plural rules
112
+ // Expansion can happen as long as 'other' form is present
113
+ const otherStr = varConfig.other;
114
+
115
+ // Translate each required target form in CLDR order
116
+ for (const form of targetPluralForms) {
117
+ const sourceText = varConfig[form] ?? otherStr;
118
+ if (sourceText === undefined) {
119
+ // Can't generate this required form - no source and no fallback
120
+ entryHasTranslations = false;
121
+ break;
122
+ }
123
+ const translation = await translator(`${key}/${varName}_${form}`, sourceText);
124
+ if (translation === undefined) {
125
+ entryHasTranslations = false;
126
+ break;
127
+ }
128
+ translatedVarConfig[form] = translation;
129
+ }
130
+
131
+ if (!entryHasTranslations) break;
132
+
133
+ translatedEntry[varName] = translatedVarConfig;
134
+ }
135
+
136
+ if (entryHasTranslations && variableMatches.length > 0) {
137
+ translatedPlist[key] = translatedEntry;
138
+ translated++;
139
+ }
140
+ }
141
+
142
+ if (translated === 0) {
143
+ return null;
144
+ }
145
+
146
+ return plist.build(translatedPlist);
147
+ }
148
+ }