There is surprisingly little documentation on how to create an custom I18n backend for Ruby. Heres an example implementation with comments to help guide you through the process
Custom Exception Handler:
module I18n
class CustomExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
raise exception.to_exception
else
super
end
end
end
end
### Exception handler to actually raise translation missing exceptions
### instead of showing 'translation missing' text
I18n.exception_handler = I18n::CustomExceptionHandler.new
Custom Backend:
module I18n
module Backend
class Custom
include I18n::Backend::Base ### required for all custom I18n backends
### if you want access to the flatten_translations method
# include I18n::Backend::Flatten
### Usage instructions for flatten_translations
# flattened_h = flatten_translations(:en, data, options.fetch(:escape, true), false)
# puts flattened_h # => en: {'common.foo.bar': "foobar"}
def translations
### I18n REQUIRED METHOD
@translations || set_translations
end
def available_locales
### I18n REQUIRED METHOD
@available_locales || set_available_locales
end
def initialized?
### I18n REQUIRED METHOD
!@translations.nil?
end
def reload!
### I18n REQUIRED METHOD
set_translations
return true
end
def translate(locale, key, options = EMPTY_HASH)
### I18n REQUIRED METHOD
split_keys = I18n.normalize_keys(locale, key, options[:scope], options[:separator])
val = translations.dig(*split_keys)
if val.blank? && options[:fallback]
if val.blank? && I18n.locale != I18n.default_locale
alternate_split_keys = split_keys.dup
alternate_split_keys[2] = I18n.default_locale.to_s
val = translations.dig(*alternate_split_keys)
end
# if val.blank?
# generic_key = "common.#{key.to_s.split(".").last}"
# if I18n.exists?(generic_key)
# return I18n.t(generic_key, fallback: false)
# end
# end
end
if val.blank?
#throw(:exception, I18n::MissingTranslation.new(locale, key, options))
return nil
else
return val
end
end
def store_translations(locale, data, options = EMPTY_HASH)
### I18n OPTIONAL METHOD
### Only required if you want to use the I18n.store_translations method
### Description: Used for storing the translations.
### Depending on your needs you can choose to:
### A. Just store this added data within memory
### OR
### B. Actually save the data to your file/db storage method
### IN-MEMORY STORE
if @translations.nil?
set_translations
end
locale = locale.to_sym
@temporary_translations ||= {}
#flattened_h = flatten_translations(locale, data, options.fetch(:escape, true), false)
#raise "#{flattened_h}" ### DEBUG
@temporary_translations[locale] ||= Concurrent::Hash.new ### Must ensure is a Concurrent::Hash as per i18n-ruby standards
@temporary_translations[locale] = @temporary_translations[locale].deep_merge(data.deep_symbolize_keys)
@translations = @translations.deep_merge(@temporary_translations)
set_available_locales
return true
end
protected
def set_translations
### CUSTOM NON-I18n METHOD
@translations = Concurrent::Hash.new ### Must ensure is a Concurrent::Hash as per i18n-ruby standards
### Load from your custom db here
db_translation_data = REDIS.get("translations").with_indifferent_access
@translations = @translations.deep_merge(db_translation_data)
if @temporary_translations
@translations = @translations.deep_merge(@temporary_translations)
end
set_available_locales
return @translations
end
def set_available_locales
### CUSTOM NON-I18n METHOD
@available_locales = translations.keys.map{|x| x.to_sym}
end
end
end
end
Backend Chain:
backend_chain = [I18n::Backend::Custom.new]
if Rails.env.development? || Rails.env.test?
### Keeps the original YAML backend as a fallback so that local development still works without a populated translation DB
backend_chain << I18n::Backend::Simple.new
### Alternative could be to always have the default locale stored within yml files
### and allow for translations on-top of the default locale file
### this would be useful for all environments. For example if a developer adds a new key
### and it is not yet translated in the db. Good for PR workflows
end
I18n.backend = I18n::Backend::Chain.new(*backend_chain)
### Manually add any additional translations here
# I18n.backend.store_translations :en, foo: {bar: {foobar: 'asd'} }
Related External Links: