# frozen_string_literal: true require 'fast_gettext/cache' module FastGettext # Responsibility: # - store data threadsafe # - provide error messages when repositories are unconfigured # - accept/reject locales that are set by the user module Storage class NoTextDomainConfigured < RuntimeError def to_s "Current textdomain (#{FastGettext.text_domain.inspect}) was not added, use FastGettext.add_text_domain !" end end DEFAULT_PLURALIZATION_RULE = ->(i) { i != 1 } [:available_locales, :_locale, :text_domain, :pluralisation_rule].each do |method_name| key = "fast_gettext_#{method_name}" eval <<-RUBY, nil, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval def #{method_name}=(value) switch_cache if Thread.current[:#{key}] != Thread.current[:#{key}] = value end RUBY end def _locale Thread.current[:fast_gettext__locale] end private :_locale, :_locale= def available_locales locales = Thread.current[:fast_gettext_available_locales] || default_available_locales return unless locales locales.map(&:to_s) end # cattr_accessor with defaults [ [:default_available_locales, "nil"], [:default_text_domain, "nil"], [:cache_class, "FastGettext::Cache"] ].each do |name, default| eval <<-RUBY, nil, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval @@#{name} = #{default} def #{name}=(value) @@#{name} = value switch_cache end def #{name} @@#{name} end RUBY end def text_domain Thread.current[:fast_gettext_text_domain] || default_text_domain end # if overwritten by user( FastGettext.pluralisation_rule = xxx) use it, # otherwise fall back to repo or to default lambda def pluralisation_rule Thread.current[:fast_gettext_pluralisation_rule] || current_repository.pluralisation_rule || DEFAULT_PLURALIZATION_RULE end def cache Thread.current[:fast_gettext_cache] ||= cache_class.new end def reload! cache.reload! translation_repositories.values.each(&:reload) end # global, since re-parsing whole folders takes too much time... @@translation_repositories = {} # rubocop:disable Style/ClassVars def translation_repositories @@translation_repositories end def current_repository translation_repositories[text_domain] || raise(NoTextDomainConfigured) end def key_exist?(key) !!(cached_find key) end def cached_find(key) cache.fetch(key) { current_repository[key] } end def cached_plural_find(*keys) key = '||||' + keys * '||||' cache.fetch(key) { current_repository.plural(*keys) } end def expire_cache_for(key) cache.delete(key) end def locale _locale || (default_locale || (available_locales || []).first || 'en') end def locale=(new_locale) set_locale(new_locale) end # for chaining: puts set_locale('xx') == 'xx' ? 'applied' : 'rejected' # returns the current locale, not the one that was supplied # like locale=(), whoes behavior cannot be changed def set_locale(new_locale) # rubocop:disable Naming/AccessorMethodName new_locale = best_locale_in(new_locale) self._locale = new_locale locale end @@default_locale = nil # rubocop:disable Style/ClassVars def default_locale=(new_locale) @@default_locale = best_locale_in(new_locale) # rubocop:disable Style/ClassVars switch_cache end def default_locale @@default_locale end # Opera: de-DE,de;q=0.9,en;q=0.8 # Firefox de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 # IE6/7 de # nil if nothing matches def best_locale_in(locales) formatted_sorted_locales(locales).each do |candidate| return candidate unless available_locales return candidate if available_locales.include?(candidate) return candidate[0..1] if available_locales.include?(candidate[0..1]) # available locales include a langauge end nil # nothing found im sorry :P end # temporarily switch locale for a block # FastGettext.with_locale 'xx' { _('cars') } def with_locale(temp_locale) current_locale = locale set_locale temp_locale yield ensure set_locale current_locale end # turn off translation if none was defined to disable all resulting errors def silence_errors require 'fast_gettext/translation_repository/base' translation_repositories[text_domain] ||= TranslationRepository::Base.new('x', path: 'locale') end private # de-de,DE-CH;q=0.9 -> ['de_DE','de_CH'] def formatted_sorted_locales(locales) found = weighted_locales(locales).reject(&:empty?).sort_by(&:last).reverse # sort them by weight which is the last entry found.flatten.map { |l| format_locale(l) } end # split the locale and seperate it into different languages # de-de,de;q=0.9,en;q=0.8 => [['de-de','de','0.5'], ['en','0.8']] def weighted_locales(locales) locales = locales.to_s.gsub(/\s/, '') found = [[]] locales.split(',').each do |part| if /;q=/.match?(part) # contains language and weight ? found.last << part.split(/;q=/) found.last.flatten! found << [] else found.last << part end end found end # de-de -> de_DE def format_locale(locale) locale.sub(/^([a-zA-Z]{2,3})[-_]([a-zA-Z]{2,3})$/) { $1.downcase + '_' + $2.upcase } end def switch_cache cache.switch_to(text_domain, locale) end end end