Skip to content

Gregorian#month_names and #weekday_names return partial data for 104 locales (en-CA, en-GB, en-IN, ...) #581

Description

@nightsurgex2

Summary

Worldwide::Calendar::Gregorian.month_names and .weekday_names return partial results for 104 locales, including en-CA, en-GB, en-IN, en-AU, en-NZ, en-ZA and every other en-001 descendant.

Worldwide::Calendar::Gregorian.month_names(locale: "en-CA", width: :abbreviated)
#=> ["Sept"]
# expected: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]

Worldwide::Calendar::Gregorian.weekday_names(locale: "se-FI")
#=> {mon: "mánnodat", tue: "disdat", thu: "duorastat", sat: "lávvordat"}
# expected: all seven, with sun/wed/fri inherited from `se`

Reproduced on worldwide 1.26.2 (current main, ad6eeefb), i18n 1.15.2, Ruby 3.4.8.

Cause

CLDR resolves locale data item by item: the effective data for a locale is the union of its own data with each of its ancestors', with the more specific locale winning per item (UTS #35 §4.1). Locale files in data/cldr/locales/ are stored as deltas accordingly — data/cldr/locales/en-CA/calendars.yml contains exactly:

months:
  stand_alone:
    abbreviated:
      9: Sept

I18n::Backend::Fallbacks#translate returns the first non-nil node it finds in the fallback chain. That is correct for leaves (months.stand_alone.abbreviated.1 still resolves to "Jan" from en) but wrong for a whole node: months.stand_alone.abbreviated matches en-CA's one-entry hash and the eleven inherited entries never get merged in.

month_names then calls .values on that hash, so the truncation is silent — lib/worldwide/calendar/gregorian.rb#L20.

Affected locales

805 known locales × 2 methods × 3 widths = 4830 combinations; 108 of them are wrong, spanning 104 distinct locales.

Method Width Count Locales
month_names :abbreviated 101 ar-DZ, ar-MA, ar-TN, en-001, en-150, en-AG, en-AI, en-AT, en-BB, en-BE, en-BM, en-BS, en-BW, en-BZ, en-CA, en-CC, en-CH, en-CK, en-CM, en-CX, en-CY, en-DE, en-DG, en-DK, en-DM, en-ER, en-FI, en-FJ, en-FK, en-FM, en-GD, en-GG, en-GH, en-GI, en-GM, en-GY, en-HK, en-IE, en-IL, en-IM, en-IN, en-IO, en-JE, en-JM, en-KE, en-KI, en-KN, en-KY, en-LC, en-LR, en-LS, en-MG, en-MO, en-MS, en-MT, en-MU, en-MV, en-MW, en-MY, en-NA, en-NF, en-NG, en-NL, en-NR, en-NU, en-NZ, en-PG, en-PK, en-PN, en-PW, en-RW, en-SB, en-SC, en-SD, en-SE, en-SG, en-SH, en-SI, en-SL, en-SS, en-SX, en-SZ, en-TC, en-TK, en-TO, en-TT, en-TV, en-TZ, en-UG, en-VC, en-VG, en-VU, en-WS, en-ZA, en-ZM, en-ZW, hi-Latn, hi-Latn-IN, se-FI, sr-Latn-ME, sr-Latn-XK
month_names :narrow 3 ar-DZ, ar-MA, ar-TN
month_names :wide 3 ar-DZ, ar-MA, ar-TN
weekday_names :wide 1 se-FI

Worst cases return a single entry: hi-Latn and se-FI return 1 of 12 abbreviated month names; sr-Latn-ME and sr-Latn-XK return 5 of 12. en-GB and friends return 1 of 12.

Secondary issue: missing structural keys degrade into a plausible string

Worldwide::I18nExceptionHandler#degraded_translation (lib/worldwide/i18n_exception_handler.rb#L51-L57) turns any unresolved key into a humanized copy of its last segment. For a structural lookup that means a missing weekday table comes back as the string "wide" rather than raising:

Worldwide::Cldr.t("calendars.gregorian.days.stand_alone.wide", locale: :some_locale_without_data)
#=> "wide"

That is a reasonable default for leaf strings in a UI, but for a hash-valued CLDR node it converts a data gap into a plausible-looking value, and month_names then fails with NoMethodError: undefined method 'values' for an instance of String some distance from the cause. (This is how the same call sites failed on worldwide < 1.25.6, before the fiber-storage fix in 76762d1 restored the CLDR fallback chain on Ruby 3.2+.)

Proposed fix

Resolve structural CLDR keys with CLDR's inheritance rules — walk the fallback chain from the least specific ancestor to the most specific, deep-merging each locale's own contribution, and resolve <alias> nodes against the requested locale rather than the ancestor they were found in. Then have weekday_names / month_names raise if an entry is still missing rather than returning degraded data.

PR: #NNN

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions