Skip to content

Commit 7367bee

Browse files
authored
fix: xcode xcstrings encoded keys (#1664)
* fix: lock ignore keys with whitespaces * chore: add changeset * chore: upd tests
1 parent 4f485e9 commit 7367bee

File tree

6 files changed

+221
-9
lines changed

6 files changed

+221
-9
lines changed

.changeset/heavy-llamas-send.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"lingo.dev": patch
3+
---
4+
5+
supp[ort keys with whitespaces

packages/cli/demo/xcode-xcstrings-v2/example.xcstrings

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
{
22
"sourceLanguage" : "en",
33
"strings" : {
4+
"%d items left" : {
5+
"comment" : "Number of items remaining - key has space and special char",
6+
"extractionState" : "manual",
7+
"localizations" : {
8+
"en" : {
9+
"stringUnit" : {
10+
"state" : "translated",
11+
"value" : "%d items remaining"
12+
}
13+
},
14+
"es" : {
15+
"stringUnit" : {
16+
"state" : "translated",
17+
"value" : "%d items remaining"
18+
}
19+
}
20+
}
21+
},
422
"api_key" : {
523
"comment" : "API key used for authentication - should not be translated",
624
"extractionState" : "manual",
@@ -128,8 +146,8 @@
128146
}
129147
}
130148
},
131-
"welcome_message" : {
132-
"comment" : "Welcome message shown on the app's home screen",
149+
"welcome message" : {
150+
"comment" : "Welcome message shown on the app's home screen - key has space",
133151
"extractionState" : "manual",
134152
"localizations" : {
135153
"en" : {

packages/cli/i18n.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
"ignoredKeys": ["item_count"]
9393
},
9494
"xcode-xcstrings-v2": {
95-
"include": ["demo/xcode-xcstrings-v2/*.xcstrings"]
95+
"include": ["demo/xcode-xcstrings-v2/example.xcstrings"],
96+
"lockedKeys": ["%d items left"]
9697
},
9798
"xml": {
9899
"include": ["demo/xml/[locale]/*.xml"]

packages/cli/i18n.lock

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,12 +539,12 @@ checksums:
539539
notification_count/count/zero: ac0137deebf6e2b972c6c714dd8658ee
540540
notification_count/count/other: 9b350a78e1c499b9ab69eb330162c8ee
541541
11b3ea8486d8e09d2bf06db1811e0490:
542-
welcome_message: 0468579ef2fbc83c9d520c2f2f1c5059
542+
welcome_message: 448b66505b7e5cdac4547cf30f742648
543543
1254631a73b754e11a1b9ca8f7362025:
544544
item_count/variations/plural: 021a2f0c489893d720d30fe4277ccdb5
545545
notification_message/stringUnit: d14316154e233634917e317452c5f42c
546546
notification_message/substitutions/count_items/variations/plural: 3db7a8a0078bc1a87ccdb095af3164a5
547-
welcome_message/stringUnit: 0468579ef2fbc83c9d520c2f2f1c5059
547+
welcome%20message/stringUnit: 0468579ef2fbc83c9d520c2f2f1c5059
548548
ee8bbe4c9de58af9c4d5e1f4373f4db1:
549549
plural_comments_thread/variations/plural: 97a71bd34c84a4a5743d658937695acb
550550
plural_complex_sentence/variations/plural: 2a1d1e020670dc56bbe319866dbbfca9
@@ -756,3 +756,62 @@ checksums:
756756
Control.Button.Finish: ffa7a10f71182b48fefed7135bee24fa
757757
Control.Button.Yes: ec580fd11a45779b039466f1e35eed2a
758758
Control.Button.No: 8c708225830b06df2d1141c536f2a0d6
759+
260f1c30bddb38efcbf007f3adf662fa:
760+
"%25lld%20unit_days/variations/plural": 6cebb166a1f103ab1d5cb94e777a654a
761+
hello%20world/stringUnit: a2aa5a2c93a8a789a3c7f089817d4c17
762+
8595fe363b79f6a8524b7b3f788f84c9:
763+
Control.Text.WelcomeDlg%23Title: 93af4b47a8c0a84ce9fb82d2ee2bca13
764+
Control.Text.WelcomeDlg%23Description: 9cbc6500217ae2a7b3ffd74a3c723493
765+
Control.Text.LicenseAgreementDlg%23Title: 3fe6d3757d7129ede78b41dd06042bf2
766+
Control.Text.LicenseAgreementDlg%23Description: dcb0c3ee6751632a4fdf2c96953f99e1
767+
Control.Text.LicenseAgreementDlg%23AcceptCheckbox: 768d9d11db014facbc1b65af8e39e260
768+
Control.Text.FolderDlg%23Title: 0076b1dd48fd382d64661a81b9961042
769+
Control.Text.FolderDlg%23Description: bc1455ec06d5f670d5084586cf2ba9de
770+
Control.Text.FolderDlg%23ChangeButton: d4c59a35bb9636f6c8ea1e8bc5c6d624
771+
Control.Text.ProgressDlg%23Title: a3040b342470f8c7b057d5db6f8b558b
772+
Control.Text.ProgressDlg%23Description: 277d8219f832a99164df480f4304716b
773+
Control.Text.ProgressDlg%23StatusLabel: 6eddeb13a4e7ccb25e21ec3f665dbcdf
774+
Control.Text.CompleteDlg%23Title: 7adc37e2e844b5a4594f9440724b2bb5
775+
Control.Text.CompleteDlg%23Description: 8c45e01cbb06e94401df2e6a944023bd
776+
Control.Text.CompleteDlg%23LaunchCheckbox: a4ff2781965a961d37c74514339cc0e2
777+
Control.Text.MaintenanceDlg%23Title: df4569e2e3e6e525954c94833e3ad2c5
778+
Control.Text.MaintenanceDlg%23ModifyButton: 108a14dd240bb930dd1b2c7615099615
779+
Control.Text.MaintenanceDlg%23RepairButton: 9368af6bdea003a293f24c8a6dc38b51
780+
Control.Text.MaintenanceDlg%23RemoveButton: dba2fe5fe9f83f8078c687f28cba4b52
781+
Property.ProductName: c215a8d46c95f00089c5ea0d17eef742
782+
Property.ProductVersion: 54a9e730e88fb16291b852274d433923
783+
Property.Manufacturer: 31ec2d348b96a835b044ab1ee28c317a
784+
Property.ARPCOMMENTS: a595ddbc7e0bab708e0b2516287fdf92
785+
Property.ARPCONTACT: a7ad445ceffe0a5172a059a9d6c985a5
786+
Control.Text.ErrorDlg%23Title: 18d4f5c54da93fa36d82b65d528c2bee
787+
Control.Text.ErrorDlg%23Description: 8a1c0b60c4a355277b5477982834ada5
788+
Control.Text.CancelDlg%23Title: 805177777f2474248fd32002327571a0
789+
Control.Text.CancelDlg%23Description: 48b33c48c57096b86971cc78d5a5cefd
790+
Control.Button.Next: 45e9f984df59a475157f2c1b26655ac4
791+
Control.Button.Back: f541015a827e37cb3b1234e56bc2aa3c
792+
Control.Button.Cancel: 2e2a849c2223911717de8caa2c71bade
793+
Control.Button.Finish: ffa7a10f71182b48fefed7135bee24fa
794+
Control.Button.Yes: ec580fd11a45779b039466f1e35eed2a
795+
Control.Button.No: 8c708225830b06df2d1141c536f2a0d6
796+
868daf6de703449b418b99d1b57173ae:
797+
head/0#content: 1308168cca4fa5d8d7a0cf24e55e93fc
798+
head/1: 3180ad6b8de344b781637750259e0f53
799+
body/0/0: 9de5fe40cbf5f851a6d2270f01fe0739
800+
body/1/0/0: c59070fe496d5e4bd0066295b63a9056
801+
body/1/0/1: 12d74865332bf1988d51e84ba67aae09
802+
body/1/0/2: 58f0e438e665c77eedc440c5a8529b1a
803+
body/1/1/0: 119e3aa396d12a5a1aa7058e0983f9b9
804+
body/1/1/1/0: 60f9a22f4200bb4620a6ff7a1797ec30
805+
body/1/1/1/1: 03846a81f16f5e4a11acfd9445ad497d
806+
body/1/1/1/2: 15aae9d70ff1fb682f7d86baca81dcc0
807+
body/1/2/0: fbd403146395526d68ac68d142a50e21
808+
body/1/2/1: da8dc7fe06175d8b805f7f565bfe2788
809+
body/1/2/2/0: 061e1acc1b9ebad9de09fd5626e813c7
810+
body/1/2/2/1: 67f022a3f9e278d065a063b5e29dd932
811+
body/1/2/2/2: 7e23f048179f6661050edaa796528fe0
812+
body/1/2/3: 635f7e9a4afc00de34f975914afbb8b8
813+
body/1/3/0: 7a7892379e31868abba9865d20be2b72
814+
body/1/3/1: 8740df822561d74d51bb30e4b39d6193
815+
body/1/3/2: 0429f12258fabbde3abaca3dd9986178
816+
body/2/0: d32e57e4a5a65f3bee8b63dcb2bfa8e7
817+
body/2/1: 7e10a8ab9cc4e6d603b3cdc48849688f

packages/cli/src/cli/loaders/index.spec.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4155,6 +4155,126 @@ Línea 3`;
41554155
});
41564156
});
41574157

4158+
describe("xcode-xcstrings-v2 bucket loader with locked keys containing spaces", () => {
4159+
it("should properly filter locked keys with spaces during pull operations", async () => {
4160+
setupFileMocks();
4161+
4162+
const input = JSON.stringify({
4163+
sourceLanguage: "en",
4164+
strings: {
4165+
"hello world": {
4166+
comment: "Greeting - should be locked",
4167+
extractionState: "manual",
4168+
localizations: {
4169+
en: {
4170+
stringUnit: {
4171+
state: "translated",
4172+
value: "Hello, world!",
4173+
},
4174+
},
4175+
es: {
4176+
stringUnit: {
4177+
state: "translated",
4178+
value: "¡Hola, mundo!",
4179+
},
4180+
},
4181+
},
4182+
},
4183+
"%lld unit_days": {
4184+
comment: "Days count - should be locked",
4185+
extractionState: "manual",
4186+
localizations: {
4187+
en: {
4188+
variations: {
4189+
plural: {
4190+
one: {
4191+
stringUnit: {
4192+
state: "translated",
4193+
value: "%lld day",
4194+
},
4195+
},
4196+
other: {
4197+
stringUnit: {
4198+
state: "translated",
4199+
value: "%lld days",
4200+
},
4201+
},
4202+
},
4203+
},
4204+
},
4205+
es: {
4206+
variations: {
4207+
plural: {
4208+
one: {
4209+
stringUnit: {
4210+
state: "translated",
4211+
value: "%lld día",
4212+
},
4213+
},
4214+
other: {
4215+
stringUnit: {
4216+
state: "translated",
4217+
value: "%lld días",
4218+
},
4219+
},
4220+
},
4221+
},
4222+
},
4223+
},
4224+
},
4225+
regular_key: {
4226+
comment: "Regular translatable key",
4227+
extractionState: "manual",
4228+
localizations: {
4229+
en: {
4230+
stringUnit: {
4231+
state: "translated",
4232+
value: "Regular",
4233+
},
4234+
},
4235+
es: {
4236+
stringUnit: {
4237+
state: "translated",
4238+
value: "Regular",
4239+
},
4240+
},
4241+
},
4242+
},
4243+
},
4244+
});
4245+
4246+
mockFileOperations(input);
4247+
4248+
// Test with lockedKeys including keys with spaces and special characters
4249+
const xcodeXcstringsV2LoaderWithLockedKeys = createBucketLoader(
4250+
"xcode-xcstrings-v2",
4251+
"i18n/[locale].xcstrings",
4252+
{
4253+
defaultLocale: "en",
4254+
},
4255+
["hello world", "%lld unit_days"], // lockedKeys with spaces and special chars
4256+
);
4257+
xcodeXcstringsV2LoaderWithLockedKeys.setDefaultLocale("en");
4258+
4259+
// First pull the default locale to initialize the loader
4260+
await xcodeXcstringsV2LoaderWithLockedKeys.pull("en");
4261+
4262+
// Pull data for translation - should filter out locked keys
4263+
const dataForTranslation =
4264+
await xcodeXcstringsV2LoaderWithLockedKeys.pull("es");
4265+
4266+
// Locked keys should be filtered out (they get flattened and encoded)
4267+
// After flat loader, keys become "hello%20world/stringUnit" and "%25lld%20unit_days/variations/plural"
4268+
expect(dataForTranslation).not.toHaveProperty("hello%20world/stringUnit");
4269+
expect(dataForTranslation).not.toHaveProperty("%25lld%20unit_days/variations/plural");
4270+
4271+
// Non-locked keys should remain (flattened)
4272+
expect(dataForTranslation).toHaveProperty("regular_key/stringUnit");
4273+
expect(dataForTranslation["regular_key/stringUnit"]).toBe("Regular");
4274+
});
4275+
4276+
});
4277+
41584278
describe("typescript bucket loader", () => {
41594279
it("should respect locked keys (pull)", async () => {
41604280
setupFileMocks();

packages/cli/src/cli/loaders/index.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ type BucketLoaderOptions = {
5858
formatter?: FormatterType;
5959
};
6060

61+
/**
62+
* Helper function to encode keys for buckets that use flat loader
63+
* The flat loader encodes keys using encodeURIComponent, so we need to
64+
* encode locked/ignored keys patterns to match against the encoded keys
65+
*/
66+
function encodeKeys(keys: string[]): string[] {
67+
return keys.map((key) => encodeURIComponent(key));
68+
}
69+
6170
export default function createBucketLoader(
6271
bucketType: Z.infer<typeof bucketTypeSchema>,
6372
bucketPathPattern: string,
@@ -270,8 +279,8 @@ export default function createBucketLoader(
270279
createXcodeXcstringsLoader(options.defaultLocale),
271280
createFlatLoader(),
272281
createEnsureKeyOrderLoader(),
273-
createLockedKeysLoader(lockedKeys || []),
274-
createIgnoredKeysLoader(ignoredKeys || []),
282+
createLockedKeysLoader(encodeKeys(lockedKeys || [])),
283+
createIgnoredKeysLoader(encodeKeys(ignoredKeys || [])),
275284
createSyncLoader(),
276285
createVariableLoader({ type: "ieee" }),
277286
createUnlocalizableLoader(options.returnUnlocalizedKeys),
@@ -285,8 +294,8 @@ export default function createBucketLoader(
285294
createXcodeXcstringsV2Loader(options.defaultLocale),
286295
createFlatLoader(),
287296
createEnsureKeyOrderLoader(),
288-
createLockedKeysLoader(lockedKeys || []),
289-
createIgnoredKeysLoader(ignoredKeys || []),
297+
createLockedKeysLoader(encodeKeys(lockedKeys || [])),
298+
createIgnoredKeysLoader(encodeKeys(ignoredKeys || [])),
290299
createSyncLoader(),
291300
createVariableLoader({ type: "ieee" }),
292301
createUnlocalizableLoader(options.returnUnlocalizedKeys),

0 commit comments

Comments
 (0)