Class: Rfc5646::Locale

Inherits:
Object
  • Object
show all
Defined in:
lib/rfc5646/locale.rb

Overview

Represents a locale that can be localized to. Locales are identified by their RFC 5646 code, which can be as simple as just a language (e.g., “en” for English), or arbitrary complex (e.g., “zh-cmn-Hans-CN” for Mandarin Chinese as spoken in China, simplified Han orthography). The entire RFC 5646 spec is supported by this class.

Constant Summary collapse

RFC5646_EXTLANG =
/(?<extlang>[a-zA-Z]{3})(-[a-zA-Z]{3}){0,2}/
RFC5646_ISO639 =
/(?<iso639>[a-zA-Z]{2,3})(-#{RFC5646_EXTLANG.source})?/
RFC5646_RESERVED =
/(?<reserved>[a-zA-Z]{4})/
RFC5646_SUBTAG =
/(?<subtag>[a-zA-Z]{5,8})/
RFC5646_REGION =
/(?<region>([a-zA-Z]{2}|\d{3}))/
RFC5646_VARIANT =
/([a-zA-Z0-9]{5,8}|\d[a-zA-Z0-9]{3})/
RFC5646_SCRIPT =
/(?<script>[a-zA-Z]{4})/
RFC5646_EXTENSION =
/([0-9A-WY-Za-wy-z](-[a-zA-Z0-9]{2,8}){1,})/
RFC5646_LANGUAGE =
/(#{RFC5646_ISO639.source}|#{RFC5646_RESERVED.source}|#{RFC5646_SUBTAG.source})/
RFC5646_PRIVATE =
/x(?<privates>(-[a-zA-Z0-9]{1,8}){1,})/
RFC5646_FORMAT =
/\A#{RFC5646_LANGUAGE.source}(-#{RFC5646_SCRIPT.source})?(-#{RFC5646_REGION.source})?(?<variants>(-#{RFC5646_VARIANT.source})*)(?<extensions>(-#{RFC5646_EXTENSION.source})*)(-#{RFC5646_PRIVATE.source})?\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(iso639, script = nil, extlang = nil, region = nil, variants = [], extensions = []) ⇒ Locale

Returns a new instance of Locale.



171
172
173
174
175
176
177
178
# File 'lib/rfc5646/locale.rb', line 171

def initialize(iso639, script = nil, extlang = nil, region = nil, variants = [], extensions = [])
  @iso639 = iso639.try!(:downcase)
  @region = region.try!(:upcase)
  @variants = variants.map(&:downcase)
  @extended_language = extlang.try!(:downcase)
  @extensions = extensions
  @script = script
end

Instance Attribute Details

#extended_languageString (readonly)

Returns The dialect (not associated with a specific region or period in time) specifier. For example, “yue” indicates Yue Chinese (Cantonese).

Returns:

  • (String)

    The dialect (not associated with a specific region or period in time) specifier. For example, “yue” indicates Yue Chinese (Cantonese).



70
71
72
# File 'lib/rfc5646/locale.rb', line 70

def extended_language
  @extended_language
end

#extensionsArray<String> (readonly)

Returns The user-defined extensions applied to this locale. The meaning of these is not specified in the spec, and left up to private use, and is ignored by this class, but stored for completeness.

Returns:

  • (Array<String>)

    The user-defined extensions applied to this locale. The meaning of these is not specified in the spec, and left up to private use, and is ignored by this class, but stored for completeness.



74
75
76
# File 'lib/rfc5646/locale.rb', line 74

def extensions
  @extensions
end

#iso639String (readonly)

Returns The ISO 639 code for the base language (e.g., “de” for German).

Returns:

  • (String)

    The ISO 639 code for the base language (e.g., “de” for German).



51
52
53
# File 'lib/rfc5646/locale.rb', line 51

def iso639
  @iso639
end

#regionString (readonly)

Returns The ISO 3166 country code for the regional dialect (e.g., “BZ” for Belize). Some special values are also supported (e.g., “013” for Central America); see the spec for details.

Returns:

  • (String)

    The ISO 3166 country code for the regional dialect (e.g., “BZ” for Belize). Some special values are also supported (e.g., “013” for Central America); see the spec for details.



58
59
60
# File 'lib/rfc5646/locale.rb', line 58

def region
  @region
end

#scriptString (readonly)

Returns The RFC 5646 code for the orthography (e.g., “Arab” for Arabic script).

Returns:

  • (String)

    The RFC 5646 code for the orthography (e.g., “Arab” for Arabic script).



54
55
56
# File 'lib/rfc5646/locale.rb', line 54

def script
  @script
end

#variantsArray<String> (readonly)

Returns The variant or nested subvariant of this locale. The full path to a subvariant is listed as a top-level Array; an example is ‘[“sl”, “rozaj”, “1994”]`, indicating the 1994 standardization of the Resian orthography of the Rozaj dialect of Slovenian (in case we should ever want to localize one of our projects thusly). Variants can be regional or temporal dialects, or orthographies, or both, and are very specific.

Returns:

  • (Array<String>)

    The variant or nested subvariant of this locale. The full path to a subvariant is listed as a top-level Array; an example is ‘[“sl”, “rozaj”, “1994”]`, indicating the 1994 standardization of the Resian orthography of the Rozaj dialect of Slovenian (in case we should ever want to localize one of our projects thusly). Variants can be regional or temporal dialects, or orthographies, or both, and are very specific.



66
67
68
# File 'lib/rfc5646/locale.rb', line 66

def variants
  @variants
end

Class Method Details

.from_rfc5646(ident) ⇒ Locale

Generates a new instance from an RFC 5646 code.

Parameters:

  • ident (String, nil)

    The RFC 5646 code for the locale.

Returns:

  • (Locale)

    The instance representing that locale.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rfc5646/locale.rb', line 81

def from_rfc5646(ident)
  ident = String(ident).tr('_', '-')
  return nil unless (matches = RFC5646_FORMAT.match(ident))
  attrs = RFC5646_FORMAT.named_captures.each_with_object({}) do |(name, offsets), hsh|
    hsh[name] = offsets.map { |offset| matches[offset] }.compact
  end

  iso639 = attrs['iso639'].first
  script = attrs['script'].first
  return nil unless iso639
  region = attrs['region'].first
  if (variants = attrs['variants'].first)
    variants = variants.split('-')
    variants.shift
  end
  if (extensions = attrs['extensions'].first)
    extensions = extensions.split('-')
    extensions.shift
  end
  extlang = attrs['extlang'].first

  Locale.new iso639, script, extlang, region, variants || [], extensions || []
end

.from_rfc5646_prefix(prefix, max = nil) ⇒ Array<Locale>

Returns an array of Locales representing all possible completions given a prefix portion of an RFC 5646 code. The resolution of the resultant array is determined by the resolution if the input prefix. Some examples:

  • If just the letter “e” is entered, Locales whose ISO 639 codes begin with the letter “e” will be returned (English, Spanish, etc.). These Locale instances will have no other fields specified.

  • If “en-U” is specified, Locale instances representing “en-US” and “en-UA”, among others, will be returned, as well as “en-Ugar” (for all the sense it makes). “en-US-Ugar” would not be returned, as it is of a higher resolution than the input.

Parameters:

  • prefix (String)

    A portion of an RFC 5646 code.

  • max (Fixnum) (defaults to: nil)

    A maximum number of completions to return.

Returns:

  • (Array<Locale>)

    Candidate completions as Locale instances.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/rfc5646/locale.rb', line 121

def from_rfc5646_prefix(prefix, max = nil)
  if prefix.include?('-')
    prefix_path = prefix.split('-')
    prefix = prefix_path.pop
    parent_prefix = prefix_path.join('-')
    parent = from_rfc5646(parent_prefix)
    return [] unless parent

    search_paths = if parent.variants.present?
                     # TODO: subvariants
                     []
                   elsif parent.region # only possible completions are variants
                     []
                     # TODO: variants
                   elsif parent.script # can be followed with variant or region
                     %w(locale.region)
                   else # can be followed with script, region, or variant
                     %w(locale.region locale.script)
                   end

    keys = search_paths.map do |path|
      I18n.t(path).select { |k, v| Locale.matches_prefix? prefix, k, v }.keys
    end.flatten
    keys.delete '_END_'
    keys = keys[0, max] if max

    return keys.map { |key| from_rfc5646 "#{parent_prefix}-#{key}" }
  else
    keys = I18n.t('locale.name').select { |k, v| Locale.matches_prefix? prefix, k, v }.keys
    keys = keys[0, max] if max
    return keys.map { |key| from_rfc5646 key }
  end
end

.matches_prefix?(prefix, key, value) ⇒ Boolean

Returns:

  • (Boolean)


156
157
158
159
160
161
# File 'lib/rfc5646/locale.rb', line 156

def matches_prefix?(prefix, key, value)
  return false unless value.is_a?(String)
  return true if key.to_s.downcase.starts_with?(prefix.downcase)
  return true if value.split(/\w+/).any? { |word| word.downcase.starts_with? prefix.downcase }
  false
end

Instance Method Details

#==(other) ⇒ true, false Also known as: eql?, equal?, ===

Tests for equality between two locales. Their full RFC 5646 codes must be equal.

Parameters:

  • other (Locale)

    Another Locale.

Returns:

  • (true, false)

    Whether it is the same Locale as the receiver.

Raises:

  • (ArgumentError)

    If ‘other` is not a Locale.



262
263
264
265
266
267
268
269
# File 'lib/rfc5646/locale.rb', line 262

def ==(other)
  case other
  when Locale
    rfc5646 == other.rfc5646
  else
    false
  end
end

#as_json(_options = nil) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/rfc5646/locale.rb', line 329

def as_json(_options = nil)
  {
    rfc5646: rfc5646,
    components: {
      iso639: iso639,
      script: script,
      extended_language: extended_language,
      region: region,
      variants: variants,
      exensions: extensions
    },
    name: name
  }
end

#child_of?(parent) ⇒ true, false

Returns whether this Languge is a subset of the given locale. “en-US” is a child of “en”.

Parameters:

  • parent (Locale)

    Another locale.

Returns:

  • (true, false)

    Whether this locale is a child of ‘parent`.



300
301
302
303
304
# File 'lib/rfc5646/locale.rb', line 300

def child_of?(parent)
  return false if iso639 != parent.iso639
  return false if parent.specificity > specificity
  parent.specified_parts.all? { |part| specified_parts.include?(part) }
end

#fallbacksArray<Locale>

Returns the fallback order for this Locale. For example, fr-CA might fall back to fr, which then falls back to en. The fallback order is described in the ‘fallbacks.yml` file.

Returns:

  • (Array<Locale>)

    The fallback order of this locale, from most specific to most general. Note that this array includes the receiver.



287
288
289
290
291
292
# File 'lib/rfc5646/locale.rb', line 287

def fallbacks
  fallbacks = Array.wrap(self.class.fallbacks[rfc5646])
              .map { |l| self.class.from_rfc5646 l }
  fallbacks.unshift self
  fallbacks
end

#hashObject



276
277
278
# File 'lib/rfc5646/locale.rb', line 276

def hash
  rfc5646.hash
end

#inspectObject



345
346
347
# File 'lib/rfc5646/locale.rb', line 345

def inspect
  "#<#{self.class} #{rfc5646}>"
end

#name(locale = nil) ⇒ String

Returns a human-readable localized name of the locale.

Parameters:

  • locale (String) (defaults to: nil)

    The locale to use (default locale is used by default).

Returns:

  • (String)

    The localized name of the locale.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/rfc5646/locale.rb', line 194

def name(locale = nil)
  I18n.with_locale(locale || I18n.locale) do
    i18n_language = if extended_language
                      I18n.t "locale.extended.#{iso639}.#{extended_language}"
                    else
                      I18n.t "locale.name.#{iso639}"
                    end

    i18n_dialect = if variants.present?
                     I18n.t "locale.variant.#{iso639}.#{variants.join '.'}._END_"
                   end

    i18n_script = script ? I18n.t("locale.script.#{script}") : nil
    i18n_region = region ? I18n.t("locale.region.#{region}") : nil

    if i18n_region && i18n_dialect && i18n_script
      I18n.t('locale.format.scripted_regional_dialectical',
             script: i18n_script,
             dialect: i18n_dialect,
             region: i18n_region,
             language: i18n_language
            )
    elsif i18n_region && i18n_dialect
      I18n.t('locale.format.regional_dialectical',
             dialect: i18n_dialect,
             region: i18n_region,
             language: i18n_language
            )
    elsif i18n_region && i18n_script
      I18n.t('locale.format.scripted_regional',
             script: i18n_script,
             region: i18n_region,
             language: i18n_language
            )
    elsif i18n_dialect && i18n_script
      I18n.t('locale.format.scripted_dialectical',
             script: i18n_script,
             dialect: i18n_dialect,
             language: i18n_language
            )
    elsif i18n_script
      I18n.t('locale.format.scripted',
             script: i18n_script,
             language: i18n_language
            )
    elsif i18n_dialect
      I18n.t('locale.format.dialectical',
             dialect: i18n_dialect,
             language: i18n_language
            )
    elsif i18n_region
      I18n.t('locale.format.regional',
             region: i18n_region,
             language: i18n_language
            )
    else
      i18n_language
    end
  end
end

#pseudo?true, false

Returns Whether this locale is a pseudo-locale.

Returns:

  • (true, false)

    Whether this locale is a pseudo-locale.



307
308
309
# File 'lib/rfc5646/locale.rb', line 307

def pseudo?
  variants.include? 'pseudo'
end

#rfc5646String Also known as: to_param

Returns The full RFC 5646 code for this locale.

Returns:

  • (String)

    The full RFC 5646 code for this locale.



182
183
184
# File 'lib/rfc5646/locale.rb', line 182

def rfc5646
  [iso639, script, extended_language, region, *variants].compact.join('-')
end

#specificityObject



312
313
314
315
316
317
318
319
# File 'lib/rfc5646/locale.rb', line 312

def specificity
  specificity = 1
  specificity += 1 if script
  specificity += 1 if region
  specificity += variants.size
  specificity += 1 if extended_language
  specificity + extensions.size
end

#specified_partsObject



322
323
324
325
326
# File 'lib/rfc5646/locale.rb', line 322

def specified_parts
  # relies on the fact that the namespace for each element of the code is
  # *globally* unique, not just unique to the code element
  (variants + extensions + [script, region, extended_language]).compact
end