diff options
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n.rb')
| -rw-r--r-- | vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n.rb | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n.rb b/vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n.rb new file mode 100644 index 0000000..429ce46 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/i18n-1.15.2/lib/i18n.rb @@ -0,0 +1,507 @@ +# frozen_string_literal: true + +require 'concurrent/map' +require 'concurrent/hash' + +require 'i18n/version' +require 'i18n/utils' +require 'i18n/exceptions' +require 'i18n/interpolate/ruby' + +module I18n + autoload :Backend, 'i18n/backend' + autoload :Config, 'i18n/config' + autoload :Gettext, 'i18n/gettext' + autoload :Locale, 'i18n/locale' + autoload :Tests, 'i18n/tests' + autoload :Middleware, 'i18n/middleware' + + RESERVED_KEYS = %i[ + cascade + deep_interpolation + skip_interpolation + default + exception_handler + fallback + fallback_in_progress + fallback_original_locale + format + object + raise + resolve + scope + separator + throw + ] + EMPTY_HASH = {}.freeze + + def self.new_double_nested_cache # :nodoc: + Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new } + end + + # Marks a key as reserved. Reserved keys are used internally, + # and can't also be used for interpolation. If you are using any + # extra keys as I18n options, you should call I18n.reserve_key + # before any I18n.translate (etc) calls are made. + def self.reserve_key(key) + RESERVED_KEYS << key.to_sym + @reserved_keys_pattern = nil + end + + def self.reserved_keys_pattern # :nodoc: + @reserved_keys_pattern ||= /(?<!%)%\{(#{RESERVED_KEYS.join("|")})\}/ + end + + module Base + # Gets I18n configuration object. + def config + if Fiber.respond_to?(:[]) + current = Fiber[:i18n_config] || self.config = I18n::Config.new + if current.respond_to?(:owned_by?) && !current.owned_by?(Fiber.current) + current = current.dup + Fiber[:i18n_config] = current + end + + current + else + Thread.current.thread_variable_get(:i18n_config) || + Thread.current.thread_variable_set(:i18n_config, I18n::Config.new) + end + end + + # Gets a mutable I18n configuration object. + def writable_config + current = config + return current unless current.frozen? + + current = current.dup + if Fiber.respond_to?(:[]) + Fiber[:i18n_config] = current + else + Thread.current.thread_variable_set(:i18n_config, current) + end + current + end + + # Sets I18n configuration object. + def config=(value) + if Fiber.respond_to?(:[]) + Fiber[:i18n_config] = value + value.owner = Fiber.current if value.respond_to?(:owner=) && !value.frozen? + else + Thread.current.thread_variable_set(:i18n_config, value) + end + end + + # Write methods which delegates to the configuration object + %w(locale backend default_locale available_locales default_separator + exception_handler load_path enforce_available_locales).each do |method| + module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1 + def #{method} + config.#{method} + end + + def #{method}=(value) + writable_config.#{method} = (value) + end + DELEGATORS + end + + # Tells the backend to reload translations. Used in situations like the + # Rails development environment. Backends can implement whatever strategy + # is useful. + def reload! + config.clear_available_locales_set + config.backend.reload! + end + + # Tells the backend to load translations now. Used in situations like the + # Rails production environment. Backends can implement whatever strategy + # is useful. + def eager_load! + config.backend.eager_load! + end + + # Translates, pluralizes and interpolates a given key using a given locale, + # scope, and default, as well as interpolation values. + # + # *LOOKUP* + # + # Translation data is organized as a nested hash using the upper-level keys + # as namespaces. <em>E.g.</em>, ActionView ships with the translation: + # <tt>:date => {:formats => {:short => "%b %d"}}</tt>. + # + # Translations can be looked up at any level of this hash using the key argument + # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt> + # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>. + # + # Key can be either a single key or a dot-separated key (both Strings and Symbols + # work). <em>E.g.</em>, the short format can be looked up using both: + # I18n.t 'date.formats.short' + # I18n.t :'date.formats.short' + # + # Scope can be either a single key, a dot-separated key or an array of keys + # or dot-separated keys. Keys and scopes can be combined freely. So these + # examples will all look up the same short date format: + # I18n.t 'date.formats.short' + # I18n.t 'formats.short', :scope => 'date' + # I18n.t 'short', :scope => 'date.formats' + # I18n.t 'short', :scope => %w(date formats) + # + # *INTERPOLATION* + # + # Translations can contain interpolation variables which will be replaced by + # values passed to #translate as part of the options hash, with the keys matching + # the interpolation variable names. + # + # <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option + # value for the key +bar+ will be interpolated into the translation: + # I18n.t :foo, :bar => 'baz' # => 'foo baz' + # + # *PLURALIZATION* + # + # Translation data can contain pluralized translations. Pluralized translations + # are arrays of singular/plural versions of translations like <tt>['Foo', 'Foos']</tt>. + # + # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English + # pluralization rules. Other algorithms can be supported by custom backends. + # + # This returns the singular version of a pluralized translation: + # I18n.t :foo, :count => 1 # => 'Foo' + # + # These both return the plural version of a pluralized translation: + # I18n.t :foo, :count => 0 # => 'Foos' + # I18n.t :foo, :count => 2 # => 'Foos' + # + # The <tt>:count</tt> option can be used both for pluralization and interpolation. + # <em>E.g.</em>, with the translation + # <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will + # be interpolated to the pluralized translation: + # I18n.t :foo, :count => 1 # => '1 foo' + # + # *DEFAULTS* + # + # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found: + # I18n.t :foo, :default => 'default' + # + # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no + # translation for <tt>:foo</tt> was found: + # I18n.t :foo, :default => :bar + # + # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> + # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found. + # I18n.t :foo, :default => [:bar, 'default'] + # + # <b>BULK LOOKUP</b> + # + # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>. + # I18n.t [:foo, :bar] + # + # Can be used with dot-separated nested keys: + # I18n.t [:'baz.foo', :'baz.bar'] + # + # Which is the same as using a scope option: + # I18n.t [:foo, :bar], :scope => :baz + # + # *LAMBDAS* + # + # Both translations and defaults can be given as Ruby lambdas. Lambdas will be + # called and passed the key and options. + # + # E.g. assuming the key <tt>:salutation</tt> resolves to: + # lambda { |key, options| options[:gender] == 'm' ? "Mr. #{options[:name]}" : "Mrs. #{options[:name]}" } + # + # Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith')</tt> will result in "Mrs. Smith". + # + # Note that the string returned by lambda will go through string interpolation too, + # so the following lambda would give the same result: + # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{name}" : "Mrs. %{name}" } + # + # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when + # a cache layer is put in front of I18n.translate it will generate a cache key + # from the argument values passed to #translate. Therefore your lambdas should + # always return the same translations/values per unique combination of argument + # values. + # + # <b>Ruby 2.7+ keyword arguments warning</b> + # + # This method uses keyword arguments. + # There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0 + # The "hash" parameter must be passed as keyword argument. + # + # Good: + # I18n.t(:salutation, :gender => 'w', :name => 'Smith') + # I18n.t(:salutation, **{ :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, **any_hash) + # + # Bad: + # I18n.t(:salutation, { :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, any_hash) + # + def translate(key = nil, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise + locale ||= config.locale + raise Disabled.new('t') if locale == false + enforce_available_locales!(locale) + + backend = config.backend + + if key.is_a?(Array) + key.map do |k| + translate_key(k, throw, raise, locale, backend, options) + end + else + translate_key(key, throw, raise, locale, backend, options) + end + end + alias :t :translate + + # Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With + # this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt> + def translate!(key, **options) + translate(key, **options, raise: true) + end + alias :t! :translate! + + # Returns an array of interpolation keys for the given translation key + # + # *Examples* + # + # Suppose we have the following: + # I18n.t 'example.zero' == 'Zero interpolations' + # I18n.t 'example.one' == 'One interpolation %{foo}' + # I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}' + # I18n.t 'example.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}'] + # I18n.t 'example.one', locale: :other == 'One interpolation %{baz}' + # + # Then we can expect the following results: + # I18n.interpolation_keys('example.zero') #=> [] + # I18n.interpolation_keys('example.one') #=> ['foo'] + # I18n.interpolation_keys('example.two') #=> ['foo', 'bar'] + # I18n.interpolation_keys('example.three') #=> ['foo', 'bar', 'baz'] + # I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz'] + # I18n.interpolation_keys('does-not-exist') #=> [] + # I18n.interpolation_keys('example') #=> [] + def interpolation_keys(key, **options) + raise I18n::ArgumentError if !key.is_a?(String) || key.empty? + + return [] unless exists?(key, **options.slice(:locale, :scope)) + + translation = translate(key, **options.slice(:locale, :scope)) + interpolation_keys_from_translation(translation) + .flatten.compact + end + + # Returns true if a translation exists for a given key, otherwise returns false. + def exists?(key, _locale = nil, locale: _locale, **options) + locale ||= config.locale + raise Disabled.new('exists?') if locale == false + raise I18n::ArgumentError if (key.is_a?(String) && key.empty?) || key.nil? + + config.backend.exists?(locale, key, options) + end + + # Transliterates UTF-8 characters to ASCII. By default this method will + # transliterate only Latin strings to an ASCII approximation: + # + # I18n.transliterate("Ærøskøbing") + # # => "AEroskobing" + # + # I18n.transliterate("日本語") + # # => "???" + # + # It's also possible to add support for per-locale transliterations. I18n + # expects transliteration rules to be stored at + # <tt>i18n.transliterate.rule</tt>. + # + # Transliteration rules can either be a Hash or a Proc. Procs must accept a + # single string argument. Hash rules inherit the default transliteration + # rules, while Procs do not. + # + # *Examples* + # + # Setting a Hash in <locale>.yml: + # + # i18n: + # transliterate: + # rule: + # ü: "ue" + # ö: "oe" + # + # Setting a Hash using Ruby: + # + # store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) + # + # Setting a Proc: + # + # translit = lambda {|string| MyTransliterator.transliterate(string) } + # store_translations(:xx, :i18n => {:transliterate => {:rule => translit}) + # + # Transliterating strings: + # + # I18n.locale = :en + # I18n.transliterate("Jürgen") # => "Jurgen" + # I18n.locale = :de + # I18n.transliterate("Jürgen") # => "Juergen" + # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen" + # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen" + def transliterate(key, throw: false, raise: false, locale: nil, replacement: nil, **options) + locale ||= config.locale + raise Disabled.new('transliterate') if locale == false + enforce_available_locales!(locale) + + config.backend.transliterate(locale, key, replacement) + rescue I18n::ArgumentError => exception + handle_exception((throw && :throw || raise && :raise), exception, locale, key, options) + end + + # Localizes certain objects, such as dates and numbers to local formatting. + def localize(object, locale: nil, format: nil, **options) + locale ||= config.locale + raise Disabled.new('l') if locale == false + enforce_available_locales!(locale) + + format ||= :default + config.backend.localize(locale, object, format, options) + end + alias :l :localize + + # Executes block with given I18n.locale set. + def with_locale(tmp_locale = nil) + if tmp_locale == nil + yield + else + current_locale = self.locale + self.locale = tmp_locale + begin + yield + ensure + self.locale = current_locale + end + end + end + + # Merges the given locale, key and scope into a single array of keys. + # Splits keys that contain dots into multiple keys. Makes sure all + # keys are Symbols. + def normalize_keys(locale, key, scope, separator = nil) + separator ||= I18n.default_separator + + [ + *normalize_key(locale, separator), + *normalize_key(scope, separator), + *normalize_key(key, separator) + ] + end + + # Returns true when the passed locale, which can be either a String or a + # Symbol, is in the list of available locales. Returns false otherwise. + def locale_available?(locale) + I18n.config.available_locales_set.include?(locale) + end + + # Raises an InvalidLocale exception when the passed locale is not available. + def enforce_available_locales!(locale) + if locale != false && config.enforce_available_locales + raise I18n::InvalidLocale.new(locale) if !locale_available?(locale) + end + end + + def available_locales_initialized? + config.available_locales_initialized? + end + + private + + def translate_key(key, throw, raise, locale, backend, options) + result = catch(:exception) do + backend.translate(locale, key, options) + end + + if result.is_a?(MissingTranslation) + handle_exception((throw && :throw || raise && :raise), result, locale, key, options) + else + result + end + end + + # Any exceptions thrown in translate will be sent to the @@exception_handler + # which can be a Symbol, a Proc or any other Object unless they're forced to + # be raised or thrown (MissingTranslation). + # + # If exception_handler is a Symbol then it will simply be sent to I18n as + # a method call. A Proc will simply be called. In any other case the + # method #call will be called on the exception_handler object. + # + # Examples: + # + # I18n.exception_handler = :custom_exception_handler # this is the default + # I18n.custom_exception_handler(exception, locale, key, options) # will be called like this + # + # I18n.exception_handler = lambda { |*args| ... } # a lambda + # I18n.exception_handler.call(exception, locale, key, options) # will be called like this + # + # I18n.exception_handler = I18nExceptionHandler.new # an object + # I18n.exception_handler.call(exception, locale, key, options) # will be called like this + def handle_exception(handling, exception, locale, key, options) + case handling + when :raise + raise exception.respond_to?(:to_exception) ? exception.to_exception : exception + when :throw + throw :exception, exception + else + case handler = options[:exception_handler] || config.exception_handler + when Symbol + send(handler, exception, locale, key, options) + else + handler.call(exception, locale, key, options) + end + end + end + + @@normalized_key_cache = I18n.new_double_nested_cache + + def normalize_key(key, separator) + @@normalized_key_cache[separator][key] ||= + case key + when Array + key.flat_map { |k| normalize_key(k, separator) } + else + keys = key.to_s.split(separator) + keys.delete('') + keys.map! do |k| + case k + when /\A[-+]?([1-9]\d*|0)\z/ # integer + k.to_i + when 'true' + true + when 'false' + false + else + k.to_sym + end + end + keys + end + end + + def interpolation_keys_from_translation(translation) + case translation + when ::String + translation.scan(Regexp.union(I18n.config.interpolation_patterns)) + when ::Array + translation.map { |element| interpolation_keys_from_translation(element) } + else + [] + end + end + end + + extend Base +end |
