app/liquid/liquid_i18n.rb
# frozen_string_literal: true
# The <tt>LiquidI18nRails</tt> module allows us to use the +translate+
# method of Rails' I18n library within liquid templates. To use it,
# simply pass the name of the text entry to the +t+ filter:
#
# {{ 'fundraiser.thank_you' | t }}
#
# The above tag is equivalent to calling:
#
# I18n.t('fundraiser.thank_you')
#
# The parsing logic parses interpolation arguments using a regex, which
# is how liquid parse all its tags. It expects values of the form:
#
# {{ 'fundraiser.donate, amount: $15, foo: bar' | t }}
#
# The +val+ method serves to facilitate variable interpolation in liquid.
# If the string "$15" is stored in a variable called my_amount, the above
# can be re-written as
#
# {{ 'fundraiser.donate' | val: 'amount', my_amount | val: 'foo','bar' | t }}
#
# Because we don't have a proof that there's no degenerate case that makes
# the iterative regex loop forever, it limits to 10 interpolations per translation.
#
# The error logic here serves to ensure aggressive reporting of missing
# translations when in development and test mode. In those environments,
# if a template is rendered and a translation is missing, a runtime error
# will be raised. In production, it will show a fallback message.
#
# Because +Liquid+ catches +StandardError+, we've created another error
# class subclassed directly on Exception that will not be caught.
class I18n::TranslationMissing < Exception; end
class I18n::TooMuchInterpolation < StandardError; end
module LiquidI18n
MAX_INTERPOLATIONS = 10
def t(query)
translation_key, interpolation_vals = parse_interpolation(query)
begin
I18n.t(translation_key, **interpolation_vals.merge(raise: !Rails.env.production?))
rescue I18n::MissingTranslationData => e
raise I18n::TranslationMissing, e.message
end
end
def i18n_date(date, format = :default)
I18n.l(Time.parse(date), format: format.to_sym)
end
def val(base, key, value)
"#{base}, #{key}: #{value}"
end
private
def parse_interpolation(query)
params = {}
depth = 0
_, translation_key, string_params = /([^,]+)(.*)/.match(query).to_a
while string_params.present? && !string_params.empty?
if depth >= MAX_INTERPOLATIONS
raise I18n::TooMuchInterpolation, "More than #{MAX_INTERPOLATIONS} interpolation values are not allowed."
end
_, key, val, string_params = /, *([a-zA-z_]+): *([^,]+)(.*)/.match(string_params).to_a
params[key.to_sym] = val if key.present? && !key.empty?
depth += 1
end
[translation_key, params]
end
end