feat: add Prism and I18n
This commit is contained in:
1 parent
bb80e7d3c4
commit
069b348302
1 file changed
+124
@@ -0,0 +1,124 @@
|
||||
require_relative 'i18n/backend'
|
||||
require_relative 'i18n/translator'
|
||||
|
||||
module Prism
|
||||
# Rails-i18n-like translation API: JSON translation files (this Ruby
|
||||
# build has no psych/libyaml, so YAML is not an option), a per-thread
|
||||
# current locale, fallback chains, and Accept-Language negotiation.
|
||||
module I18n
|
||||
class << self
|
||||
def backend
|
||||
@backend ||= Backend.new
|
||||
end
|
||||
|
||||
def default_locale
|
||||
@default_locale ||= :en
|
||||
end
|
||||
|
||||
def default_locale=(locale)
|
||||
@translator = nil
|
||||
@default_locale = locale.to_sym
|
||||
end
|
||||
|
||||
def fallbacks
|
||||
@fallbacks ||= {}
|
||||
end
|
||||
|
||||
def fallbacks=(hash)
|
||||
@translator = nil
|
||||
@fallbacks = hash
|
||||
end
|
||||
|
||||
def locale
|
||||
Thread.current[:prism_i18n_locale] || default_locale
|
||||
end
|
||||
|
||||
def locale=(new_locale)
|
||||
Thread.current[:prism_i18n_locale] = new_locale&.to_sym
|
||||
end
|
||||
|
||||
def with_locale(new_locale)
|
||||
previous = locale
|
||||
self.locale = new_locale
|
||||
yield
|
||||
ensure
|
||||
self.locale = previous
|
||||
end
|
||||
|
||||
def load_path
|
||||
@load_path ||= []
|
||||
end
|
||||
|
||||
def load_translations!
|
||||
load_path.each do |path|
|
||||
Dir.glob(File.join(path, '**', '*.json')).sort.each { |file| backend.load_file(file) }
|
||||
end
|
||||
@translator = nil
|
||||
end
|
||||
|
||||
def store_translations(locale, data)
|
||||
@translator = nil
|
||||
backend.store_translations(locale, data)
|
||||
end
|
||||
|
||||
def available_locales
|
||||
backend.locales
|
||||
end
|
||||
|
||||
def translate(key, **options)
|
||||
options[:locale] ||= locale
|
||||
translator.translate(key, **options)
|
||||
end
|
||||
alias_method :t, :translate
|
||||
|
||||
def localize(value, **options)
|
||||
options[:locale] ||= locale
|
||||
translator.localize(value, **options)
|
||||
end
|
||||
alias_method :l, :localize
|
||||
|
||||
# Picks the first locale in the client's Accept-Language header that
|
||||
# this app has translations for, honoring q-values (like
|
||||
# Mime.best_from_accept but for BCP-47 language tags, matching on
|
||||
# the primary subtag: "en-US" negotiates against an :en backend).
|
||||
def best_locale_from_accept(header, available: available_locales)
|
||||
return nil if header.nil? || header.strip.empty?
|
||||
wanted = available.map(&:to_s)
|
||||
parse_accept_language(header).each do |tag|
|
||||
exact = wanted.find { |candidate| candidate.casecmp?(tag) }
|
||||
return exact.to_sym if exact
|
||||
primary = tag.split('-').first
|
||||
match = wanted.find { |candidate| candidate.casecmp?(primary) }
|
||||
return match.to_sym if match
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def reset!
|
||||
@backend = nil
|
||||
@translator = nil
|
||||
@default_locale = nil
|
||||
@fallbacks = nil
|
||||
@load_path = nil
|
||||
Thread.current[:prism_i18n_locale] = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def translator
|
||||
@translator ||= Translator.new(backend, default_locale: default_locale, fallbacks: fallbacks)
|
||||
end
|
||||
|
||||
def parse_accept_language(header)
|
||||
entries = header.split(',').filter_map do |part|
|
||||
tag, *params = part.strip.split(';').map(&:strip)
|
||||
next if tag.nil? || tag.empty?
|
||||
quality = 1.0
|
||||
params.each { |param| quality = param.sub('q=', '').to_f if param.start_with?('q=') }
|
||||
[tag, quality]
|
||||
end
|
||||
entries.sort_by.with_index { |(_, quality), index| [-quality, index] }.map(&:first)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user