app/extensions/symbol_extensions.rb
# frozen_string_literal: true
#
# = Extensions to Symbol
# == Instance Methods
#
# localize:: Wrapper on I18n#localize.
# l:: Alias for localize.
# t:: Localize, textilize (no paragraphs or obj links).
# tl:: Localize, textilize with obj links (no paragraphs).
# tp:: Localize, textilize with paragraphs (no obj links).
# tpl:: Localize, textilize with paragraphs and obj links.
#
# upcase_first Capitalize 1st letter of Symbol, leaving remainder alone
#
################################################################################
#
class Symbol
@raise_errors = false
def self.raise_errors(turn_on = true)
@raise_errors = turn_on
end
def self.raise_error?
@raise_errors
end
# Capitalizes just the first letter of Symbol, leaving remainder alone
# (Symbol#capitalize capitalizes first letter, downcases the rest)
def upcase_first
to_s.upcase_first.to_sym
end
def add_leaf(*)
Tree.add_leaf(self, *)
end
def has_node?(*)
Tree.has_node?(self, *)
end
# Return a list of missing tags we've encountered.
def self.missing_tags
@@missing_tags = [] unless defined?(@@missing_tags)
@@missing_tags
end
# Reset the list of missing tags.
def self.missing_tags=(tags)
@@missing_tags = tags
end
# Does this tag have a translation?
def has_translation?
(I18n.t("#{MO.locale_namespace}.#{self}", default: "BOGUS_DEFAULT") !=
"BOGUS_DEFAULT") ||
(I18n.t("#{MO.locale_namespace}.#{downcase}", default: "BOGUS_DEFAULT") !=
"BOGUS_DEFAULT")
end
# Wrapper on the old +localize+ method that:
# 1. converts '\n' into newline throughout
# 2. maps '[arg]' via optional hash you can pass in
# 3. expands recursively-embedded tags like '[:tag]' and '[:tag(:key=val)]'
# or '[:tag1(:key=>:tag2)]' (but no deeper).
#
# There are several related wrappers on localize:
# t:: Textilize: no paragraphs or links or anything fancy.
# tl:: Do "t" and check for links.
# tp:: Wrap "t" in a <p> block.
# tpl:: Wrap "t" in a <p> block AND do links.
#
# Note that these are guaranteed to be "sanitary".
# The strings in the translation
# files are assumed to be same, even if they contain HTML.
# Strings passed in via the '[arg]' syntax have any HTML stripped from them.
#
# ==== Argument expansion
#
# It supports some limited attempts to get case and number correct. This
# feature only works if the value passed in is a Symbol. For example:
#
# :tag.l(:type => :login)
#
# # Given these definitions:
# LOGIN: Nombre de Usuario
# login: nombre de usuario
# LOGINS: Nombres de Usuario
# logins: nombres de usuario
#
# # Yields these:
# tag1: Por favor, entrar [type] => Por favor, entrar nombre de usuario
# tag2: Ningun [types] encontrados => Ningun nombres de usuario encontrados
# tag3: [Type] desaparecidos => Nombre de usuario desaparecidos
# tag4: [Types] son necesarios => Nombres de usuario son necesarios
# title1: Cambio de [TYPE] => Cambio de Nombre de Usuario
# title2: Todos los [TYPES] => Todos los Nombres de Usuario
#
# This allows German to capitalize all of the above, since all major nouns
# are capitalized even in normal sentences. And it allows the translator to
# use plural if it makes more sense in their language, even if we used
# singular in English. And it allows translators to feel free to place a
# word at the beginning of the sentece even if we didn't in English.
#
# *NOTE*: No case is enforced on literal Strings passed in, such as, for
# example, user or observation names.
#
# ==== Recursively-Embedded Tags::
#
# Tag definitions are allowed to embed references to other tags.
#
# [:alpha]
# [:alpha(alpha=almost_anything,alpha=almost_anything,...)]
#
# Where "almost_anything" is anything but , = () [] or \n. If it looks like
# a symbol (i.e. is ":alpha"), then it is converted into one. If it is just
# alphanumeric, then it is assumed to be an arg from the parent's hash.
# Otherwise it must start and end with single-quotes. Any additional single
# quotes inside are preserved as-is.
#
# [:tag(type=:name)] == :tag.l(type: :name)
# [:tag(type=parent_arg)] == :tag.l(type: args[:parent_arg])
# [:tag(type='Literal Value')] == :tag.l(type: "Literal Value")
# [:tag(type=""Quote's Are Kept"")] == :tag.l(type: ""Quote"s Are Kept'")
#
# *NOTE*: Square brackets are NOT allowed in the literals, even if quoted!
# That would make the parsing non-trivial and potentially slow.
#
def localize(args = {}, level = [])
result = nil
Language.note_usage_of_tag(self)
if (val = I18n.t("#{MO.locale_namespace}.#{self}", default: "")) != ""
result = localize_postprocessing(val, args, level)
elsif (val = I18n.t("#{MO.locale_namespace}.#{downcase}",
default: "")) != ""
result = localize_postprocessing(val, args, level, :captialize)
else
@@missing_tags << self if defined?(@@missing_tags)
if args.any?
pairs = []
args.each do |k, v|
pairs << "#{k}=#{v.inspect}"
end
args_str = "(#{pairs.join(",")})"
else
args_str = ""
end
result = "[:#{self}#{args_str}]"
end
# (I guess some "factory-installed" translations can actually return
# Hashes instead of strings. Don't ask me. This just prevents it from
# crashing in those cases at least.)
result.is_a?(Hash) ? "".html_safe : result.html_safe
end
# Run +localize+ in test mode.
def self.test_localize(val, args = {}, level = [])
:test.localize_postprocessing(val, args, level)
end
def localize_postprocessing(val, args, level, capitalize_result = false)
result = val
if result.is_a?(String)
result = result.gsub(/ *\\n */, "\n")
if args.is_a?(Hash)
result = localize_expand_arguments(result, args, level)
end
if level.length < 8
result = localize_recursive_expansion(result, args, level)
end
end
if result.is_a?(String)
# Allow literal square brackets by doubling them.
result = result.gsub("[[", "[").gsub("]]", "]")
end
if capitalize_result
# Make token attempt to capitalize result if requested [:TAG] for :tag.
result = result.upcase_first
end
result
end
def localize_expand_arguments(val, args, _level) # :nodoc:
val.gsub(/\[(\[?\w+?)\]/) do
orig = x = y = Regexp.last_match(1)
# Ignore double-brackets.
if x[0, 1] == "["
x
# Want :type, given :type.
elsif args.key?(arg = x.to_sym)
val = args[arg]
val.is_a?(Symbol) ? val.l : val.to_s.strip_html
# Want :types, given :type.
elsif (y = x.sub(/s$/i, "")) &&
args.key?(arg = y.to_sym)
val = args[arg]
val.is_a?(Symbol) ? :"#{val}s".l : val.to_s.strip_html
# Want :TYPE, given :type.
elsif args.key?(arg = x.downcase.to_sym) &&
(x == x.upcase)
val = args[arg]
if val.is_a?(Symbol)
val.to_s.upcase.to_sym.l
else
val.to_s.strip_html.upcase_first
end
# Want :TYPES, given :type.
elsif args.key?(arg = y.downcase.to_sym) &&
(y == y.upcase)
val = args[arg]
if val.is_a?(Symbol)
:"#{val.to_s.upcase}S".l
else
val.to_s.strip_html.upcase_first
end
# Want :Type, given :type.
elsif args.key?(arg = x.downcase.to_sym)
val = args[arg]
if val.is_a?(Symbol)
val.l.upcase_first
else
val.to_s.strip_html.upcase_first
end
# Want :Types, given :type.
elsif args.key?(arg = y.downcase.to_sym)
val = args[arg]
if val.is_a?(Symbol)
:"#{val}s".l.upcase_first
else
val.to_s.strip_html.upcase_first
end
else
"[#{orig}]"
end
end
end
def localize_recursive_expansion(val, args, level) # :nodoc:
val.gsub(/ \[ :(\w+?) (?:\( ([^()\[\]]+) \))? \] /x) do
tag = Regexp.last_match(1).to_sym
args2 = Regexp.last_match(2).to_s
hash = args.dup
if args2.present?
args2.split(",").each do |pair|
if pair =~ /^:?([a-z]+)=(.*)$/
key = Regexp.last_match(1).to_sym
val = Regexp.last_match(2).to_s
if val =~ /^:(\w+)$/
val = Regexp.last_match(1).to_sym
elsif val.match(/^"(.*)"$/) ||
val.match(/^'(.*)'$/) ||
val.match(/^(-?\d+(\.\d+)?)$/)
val = Regexp.last_match(1)
elsif !val.match(/^([a-z][a-z_]*\d*)$/) && Symbol.raise_error?
raise(ArgumentError.new("Invalid argument value \":#{val}\" in " \
"#{I18n.locale} localization for " +
([self] + level).map(&:inspect).join(" --> ")))
elsif !args.key?(val.to_sym) && Symbol.raise_error?
raise(ArgumentError.new("Forgot to pass :#{val} into " \
"#{I18n.locale} localization for " +
([self] + level).map(&:inspect).join(" --> ")))
else
val = args[val.to_sym]
end
hash[key] = val
elsif Symbol.raise_error?
raise(ArgumentError.new("Invalid syntax at \"#{pair}\" in " \
"arguments for #{I18n.locale} tag :#{tag} embedded in " +
([self] + level).map(&:inspect).join(" --> ")))
end
end
end
tag.l(hash, level + [self])
end
end
alias l localize
def t(*)
localize(*).t(false)
end
def tl(*)
localize(*).tl(false)
end
def tp(*)
localize(*).tp(false)
end
def tpl(*)
localize(*).tpl(false)
end
def strip_html(*)
localize(*).strip_html
end
end