Skip to content

Commit e072f82

Browse files
authored
Initiating a Locale/Language with empty language code/script/region results in an unreasonable locale. (#1102)
* Initiating a Locale/Language with empty language code/script/region results in an unreasonable locale. Here are some examples: ```swift print(Locale(languageCode: "", script: "", languageRegion: "").identifier) // "-_" let languageComponents = Locale.Language.Components(language: .init(identifier:"")) print(Locale(languageComponents: languageComponents).identifier) // "-Latn" ``` Fix this by handling empty identifiers passed in at initialization time correctly. resolves 132353443 * Use `isEmpty` check for empty strings
1 parent 3c84204 commit e072f82

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

Sources/FoundationEssentials/Locale/Locale+Language.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ extension Locale {
3838
if let languageCode = languageCode {
3939
result += languageCode._normalizedIdentifier
4040
}
41-
if let script = script {
41+
if let script = script, !script.identifier.isEmpty {
4242
result += "-"
4343
result += script._normalizedIdentifier
4444
}
45-
if let region = region {
45+
if let region = region, !region.identifier.isEmpty {
4646
result += "_"
4747
result += region._normalizedIdentifier
4848
}

Sources/FoundationInternationalization/Locale/Locale+Components_ICU.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,11 @@ extension Locale.Language {
525525
public var minimalIdentifier : String {
526526
let componentsIdentifier = components.identifier
527527

528+
guard !componentsIdentifier.isEmpty else {
529+
// Just return "". Nothing to reduce.
530+
return componentsIdentifier
531+
}
532+
528533
let localeIDWithLikelySubtags = _withFixedCharBuffer { buffer, size, status in
529534
return uloc_minimizeSubtags(componentsIdentifier, buffer, size, &status)
530535
}
@@ -543,6 +548,11 @@ extension Locale.Language {
543548
/// Returns a BCP-47 identifier that always includes the script: "zh-Hant-TW", "en-Latn-US"
544549
public var maximalIdentifier : String {
545550
let id = components.identifier
551+
guard !id.isEmpty else {
552+
// Just return "" instead of trying to fill it up
553+
return id
554+
}
555+
546556
let localeIDWithLikelySubtags = _withFixedCharBuffer { buffer, size, status in
547557
return uloc_addLikelySubtags(id, buffer, size, &status)
548558
}

Tests/FoundationInternationalizationTests/LocaleTests.swift

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ final class LocaleTests : XCTestCase {
165165
return Locale.Components(identifier: "")
166166
}
167167

168+
verify(cldr: "root", bcp47: "und", icu: "") {
169+
return Locale.Components(languageCode: "", script: "", languageRegion: "")
170+
}
171+
172+
verify(cldr: "root", bcp47: "und", icu: "") {
173+
return Locale.Components(languageCode: nil, script: nil, languageRegion: nil)
174+
}
175+
168176
verify(cldr: "und_US", bcp47: "und-US", icu: "_US") {
169177
return Locale.Components(languageRegion: .unitedStates)
170178
}
@@ -370,6 +378,110 @@ final class LocaleTests : XCTestCase {
370378
let result = Locale.identifier(fromWindowsLocaleCode: -1)
371379
XCTAssertNil(result)
372380
}
381+
382+
func test_emptyComponents() throws {
383+
384+
let emptyLocale = Locale(identifier: "")
385+
XCTAssertEqual(emptyLocale.language.languageCode, nil)
386+
XCTAssertEqual(emptyLocale.language.script, nil)
387+
XCTAssertEqual(emptyLocale.language.region, nil)
388+
XCTAssertEqual(emptyLocale.language.maximalIdentifier, "")
389+
XCTAssertEqual(emptyLocale.language.minimalIdentifier, "")
390+
XCTAssertEqual(emptyLocale.identifier, "")
391+
392+
let localeFromEmptyComp = Locale(components: Locale.Components(identifier: ""))
393+
XCTAssertEqual(localeFromEmptyComp.language.languageCode, nil)
394+
XCTAssertEqual(localeFromEmptyComp.language.script, nil)
395+
XCTAssertEqual(localeFromEmptyComp.language.region, nil)
396+
XCTAssertEqual(localeFromEmptyComp.language.maximalIdentifier, "")
397+
XCTAssertEqual(localeFromEmptyComp.language.minimalIdentifier, "")
398+
XCTAssertEqual(localeFromEmptyComp.identifier, "")
399+
400+
let localeFromEmptyLanguageComponent = Locale(languageComponents: .init(identifier: ""))
401+
XCTAssertEqual(localeFromEmptyLanguageComponent.language.languageCode, nil)
402+
XCTAssertEqual(localeFromEmptyLanguageComponent.language.script, nil)
403+
XCTAssertEqual(localeFromEmptyLanguageComponent.language.region, nil)
404+
XCTAssertEqual(localeFromEmptyLanguageComponent.language.maximalIdentifier, "")
405+
XCTAssertEqual(localeFromEmptyLanguageComponent.language.minimalIdentifier, "")
406+
XCTAssertEqual(localeFromEmptyLanguageComponent.identifier, "")
407+
408+
let localeFromEmptyLanguageComponentIndividual = Locale(languageComponents: .init(languageCode: "", script: "", region: ""))
409+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.languageCode, nil)
410+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.script, nil)
411+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.region, nil)
412+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.maximalIdentifier, "")
413+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.minimalIdentifier, "")
414+
XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.identifier, "")
415+
416+
let localeFromEmptyIndividualLanguageComponent = Locale(languageCode: "", script: "", languageRegion: "")
417+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.languageCode, nil)
418+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.script, nil)
419+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.region, nil)
420+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.maximalIdentifier, "")
421+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.minimalIdentifier, "")
422+
XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.identifier, "")
423+
424+
// Locale.Component
425+
let compFromEmptyLocale = Locale.Components(locale: emptyLocale)
426+
XCTAssertEqual(compFromEmptyLocale.languageComponents.languageCode, nil)
427+
XCTAssertEqual(compFromEmptyLocale.languageComponents.script, nil)
428+
XCTAssertEqual(compFromEmptyLocale.languageComponents.region, nil)
429+
430+
let emptyComp = Locale.Components(identifier: "")
431+
XCTAssertEqual(emptyComp.languageComponents.languageCode, nil)
432+
XCTAssertEqual(emptyComp.languageComponents.script, nil)
433+
XCTAssertEqual(emptyComp.languageComponents.region, nil)
434+
435+
// Language
436+
let emptyLanguage = Locale.Language(identifier: "")
437+
XCTAssertEqual(emptyLanguage.languageCode, nil)
438+
XCTAssertEqual(emptyLanguage.script, nil)
439+
XCTAssertEqual(emptyLanguage.region, nil)
440+
XCTAssertEqual(emptyLanguage.maximalIdentifier, "")
441+
XCTAssertEqual(emptyLanguage.minimalIdentifier, "")
442+
443+
let languageFromEmptyComponents = Locale.Language(components: .init(identifier: ""))
444+
XCTAssertEqual(languageFromEmptyComponents.languageCode, nil)
445+
XCTAssertEqual(languageFromEmptyComponents.script, nil)
446+
XCTAssertEqual(languageFromEmptyComponents.region, nil)
447+
XCTAssertEqual(languageFromEmptyComponents.maximalIdentifier, "")
448+
XCTAssertEqual(languageFromEmptyComponents.minimalIdentifier, "")
449+
450+
let languageFromEmptyComponents2 = Locale.Language(components: .init(languageCode: "", script: "", region: ""))
451+
XCTAssertEqual(languageFromEmptyComponents2.languageCode, "")
452+
XCTAssertEqual(languageFromEmptyComponents2.script, "")
453+
XCTAssertEqual(languageFromEmptyComponents2.region, "")
454+
XCTAssertEqual(languageFromEmptyComponents2.maximalIdentifier, "")
455+
XCTAssertEqual(languageFromEmptyComponents2.minimalIdentifier, "")
456+
457+
// Language.Component
458+
let languageCompFromEmptyLanguage = Locale.Language.Components(language: Locale.Language(identifier: ""))
459+
XCTAssertEqual(languageCompFromEmptyLanguage.languageCode, nil)
460+
XCTAssertEqual(languageCompFromEmptyLanguage.script, nil)
461+
XCTAssertEqual(languageCompFromEmptyLanguage.region, nil)
462+
463+
let emptyLanguageComponents = Locale.Language.Components(identifier: "")
464+
XCTAssertEqual(emptyLanguageComponents.languageCode, nil)
465+
XCTAssertEqual(emptyLanguageComponents.script, nil)
466+
XCTAssertEqual(emptyLanguageComponents.region, nil)
467+
468+
let emptyLanguageComponents2 = Locale.Language.Components(languageCode: "", script: "", region: "")
469+
XCTAssertEqual(emptyLanguageComponents2.languageCode, "")
470+
XCTAssertEqual(emptyLanguageComponents2.script, "")
471+
XCTAssertEqual(emptyLanguageComponents2.region, "")
472+
}
473+
474+
func test_nilComponents() {
475+
let nilLanguageComponents = Locale.Language.Components(languageCode: nil, script: nil, region: nil)
476+
XCTAssertEqual(nilLanguageComponents.languageCode, nil)
477+
XCTAssertEqual(nilLanguageComponents.script, nil)
478+
XCTAssertEqual(nilLanguageComponents.region, nil)
479+
480+
let nilLanguage = Locale.Language(languageCode: nil, script: nil, region: nil)
481+
XCTAssertEqual(nilLanguage.languageCode, nil)
482+
XCTAssertEqual(nilLanguage.script, nil)
483+
XCTAssertEqual(nilLanguage.region, nil)
484+
}
373485
}
374486

375487
final class LocalePropertiesTests : XCTestCase {
@@ -417,6 +529,8 @@ final class LocalePropertiesTests : XCTestCase {
417529
verify(components: Locale.Components(languageCode: "zh", script: "Hant", languageRegion: "TW"), identifier: "zh_TW")
418530
verify(components: Locale.Components(languageCode: "zh", script: "Hans", languageRegion: "TW"), identifier: "zh-Hans_TW")
419531

532+
verify(components: .init(languageCode: "", script: "", languageRegion: ""), identifier: "")
533+
420534
var custom = Locale.Components(languageCode: "en", languageRegion: "US")
421535
custom.measurementSystem = .metric
422536
custom.currency = "GBP"

0 commit comments

Comments
 (0)