class Rfc5646::Locale
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.
Constants
- RFC5646_EXTENSION
@private
- RFC5646_EXTLANG
@private
- RFC5646_FORMAT
@private
- RFC5646_ISO639
@private
- RFC5646_LANGUAGE
@private
- RFC5646_PRIVATE
@private
- RFC5646_REGION
@private
- RFC5646_RESERVED
@private
- RFC5646_SCRIPT
@private
- RFC5646_SUBTAG
@private
- RFC5646_VARIANT
@private
Attributes
@return [String] The dialect (not associated with a specific region or
period in time) specifier. For example, "yue" indicates Yue Chinese (Cantonese).
@return [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.
@return [String] The ISO 639 code for the base language (e.g., “de” for
German).
@return [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.
@return [String] The RFC 5646 code for the orthography (e.g., “Arab” for
Arabic script).
@return [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.
Public Class Methods
Generates a new instance from an RFC 5646 code.
@param [String, nil] ident The RFC 5646 code for the locale. @return [Locale] The instance representing that locale.
# 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
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.
@param [String] prefix A portion of an RFC 5646 code. @param [Fixnum] max A maximum number of completions to return. @return [Array<Locale>] Candidate completions as Locale
instances.
# 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
@private
# 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
@private
# 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
Private Class Methods
# File lib/rfc5646/locale.rb, line 165 def fallbacks @fallbacks ||= YAML.load_file(Rails.root.join('data', 'fallbacks.yml')) end
Public Instance Methods
Tests for equality between two locales. Their full RFC 5646 codes must be equal.
@param [Locale] other Another Locale
. @return [true, false] Whether it is the same Locale
as the receiver. @raise [ArgumentError] If ‘other` is not a Locale
.
# File lib/rfc5646/locale.rb, line 262 def ==(other) case other when Locale rfc5646 == other.rfc5646 else false end end
@private
# 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
Returns whether this Languge is a subset of the given locale. “en-US” is a child of “en”.
@param [Locale] parent Another locale. @return [true, false] Whether this locale is a child of ‘parent`.
# 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
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.
@return [Array<Locale>] The fallback order of this locale, from most
specific to most general. Note that this array includes the receiver.
# 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
@private
# File lib/rfc5646/locale.rb, line 276 def hash rfc5646.hash end
@private
# File lib/rfc5646/locale.rb, line 345 def inspect "#<#{self.class} #{rfc5646}>" end
Returns a human-readable localized name of the locale.
@param [String] locale The locale to use (default locale is used by
default).
@return [String] The localized name of the locale.
# 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
@return [true, false] Whether this locale is a pseudo-locale.
# File lib/rfc5646/locale.rb, line 307 def pseudo? variants.include? 'pseudo' end
@return [String] The full RFC 5646 code for this locale.
# File lib/rfc5646/locale.rb, line 182 def rfc5646 [iso639, script, extended_language, region, *variants].compact.join('-') end
@private
# 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
@private
# 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