slim 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +3 -1
- data/CHANGES +6 -0
- data/Gemfile +1 -3
- data/README.md +504 -207
- data/Rakefile +28 -12
- data/benchmarks/profile-render.rb +3 -3
- data/benchmarks/run-benchmarks.rb +104 -62
- data/benchmarks/run-diffbench.rb +21 -0
- data/bin/slimrb +1 -2
- data/lib/slim.rb +0 -2
- data/lib/slim/command.rb +12 -5
- data/lib/slim/compiler.rb +10 -15
- data/lib/slim/embedded_engine.rb +34 -16
- data/lib/slim/engine.rb +4 -51
- data/lib/slim/filter.rb +5 -5
- data/lib/slim/grammar.rb +1 -1
- data/lib/slim/interpolation.rb +1 -3
- data/lib/slim/logic_less.rb +6 -0
- data/lib/slim/{sections.rb → logic_less/filter.rb} +16 -16
- data/lib/slim/logic_less/wrapper.rb +64 -0
- data/lib/slim/parser.rb +9 -11
- data/lib/slim/translator.rb +117 -0
- data/lib/slim/version.rb +1 -1
- data/slim.gemspec +1 -1
- data/test/slim/helper.rb +0 -5
- data/test/slim/{test_sections.rb → logic_less/test_logic_less.rb} +16 -15
- data/test/slim/{test_wrapper.rb → logic_less/test_wrapper.rb} +4 -12
- data/test/slim/test_encoding.rb +6 -0
- data/test/slim/test_slim_template.rb +4 -0
- data/test/slim/translator/test_translator.rb +64 -0
- metadata +69 -24
- data/lib/slim/wrapper.rb +0 -57
data/lib/slim/engine.rb
CHANGED
@@ -2,12 +2,9 @@ module Slim
|
|
2
2
|
# Slim engine which transforms slim code to executable ruby code
|
3
3
|
# @api public
|
4
4
|
class Engine < Temple::Engine
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# Slim::Engine.set_default_options :pretty => true
|
9
|
-
#
|
10
|
-
# This overwrites some temple default options.
|
5
|
+
# This overwrites some Temple default options or sets default options for Slim specific filters.
|
6
|
+
# It is recommended to set the default settings only once in the code and avoid duplication. Only use
|
7
|
+
# `set_default_options` when you have to override some default settings.
|
11
8
|
set_default_options :pretty => false,
|
12
9
|
:sort_attrs => true,
|
13
10
|
:attr_wrapper => '"',
|
@@ -15,54 +12,10 @@ module Slim
|
|
15
12
|
:remove_empty_attrs => true,
|
16
13
|
:generator => Temple::Generators::ArrayBuffer,
|
17
14
|
:default_tag => 'div'
|
18
|
-
|
19
|
-
# Document all supported options with purpose, type etc.
|
20
|
-
#
|
21
|
-
# Type | Name | Default value | Purpose
|
22
|
-
# --------------------------------------------------------------------------------------------------------------------------------------------
|
23
|
-
# String | :file | nil | Name of parsed file, set automatically by Slim::Template
|
24
|
-
# Integer | :tabsize | 4 | Number of whitespaces per tab (used by the parser)
|
25
|
-
# String | :encoding | "utf-8" | Set encoding of template
|
26
|
-
# String | :default_tag | "div" | Default tag to be used if tag name is omitted
|
27
|
-
# Hash | :shortcut | {'.' => 'class', ...} | Attribute shortcuts
|
28
|
-
# String list | :enable_engines | All enabled | List of enabled embedded engines (whitelist)
|
29
|
-
# String list | :disable_engines | None disabled | List of disabled embedded engines (blacklist)
|
30
|
-
# Boolean | :sections | false | Enable sections mode (logic-less)
|
31
|
-
# String | :dictionary | "self" | Name of dictionary variable in sections mode
|
32
|
-
# Symbol | :dictionary_access | :wrapped | Access mode of dictionary variable (:wrapped, :symbol, :string)
|
33
|
-
# Boolean | :disable_capture | false (true in Rails) | Disable capturing in blocks (blocks write to the default buffer
|
34
|
-
# Boolean | :disable_escape | false | Disable automatic escaping of strings
|
35
|
-
# Boolean | :use_html_safe | false (true in Rails) | Use String#html_safe? from ActiveSupport (Works together with :disable_escape)
|
36
|
-
# Symbol | :format | :xhtml | HTML output format
|
37
|
-
# String | :attr_wrapper | '"' | Character to wrap attributes in html (can be ' or ")
|
38
|
-
# Hash | :attr_delimiter | {'class' => ' '} | Joining character used if multiple html attributes are supplied (e.g. id1_id2)
|
39
|
-
# Boolean | :sort_attrs | true | Sort attributes by name
|
40
|
-
# Boolean | :remove_empty_attrs| true | Remove attributes with empty value
|
41
|
-
# Boolean | :pretty | false | Pretty html indenting (This is slower!)
|
42
|
-
# String | :indent | ' ' | Indentation string
|
43
|
-
# Boolean | :streaming | false (true in Rails > 3.1) | Enable output streaming
|
44
|
-
# Class | :generator | ArrayBuffer/RailsOutputBuffer | Temple code generator (default generator generates array buffer)
|
45
|
-
#
|
46
|
-
# It is also possible to set all options supported by the generator (option :generator). The standard generators
|
47
|
-
# support the options :buffer and :capture_generator.
|
48
|
-
#
|
49
|
-
# Options can be set at multiple positions. Slim/Temple uses a inheritance mechanism to allow
|
50
|
-
# subclasses to overwrite options of the superclass. The option priorities are as follows:
|
51
|
-
#
|
52
|
-
# Custom (Options passed by the user) > Slim::Template > Slim::Engine > Parser/Filter/Generator (e.g Slim::Parser, Slim::Compiler)
|
53
|
-
#
|
54
|
-
# It is also possible to set options for superclasses like Temple::Engine. But this will affect all temple template engines then.
|
55
|
-
#
|
56
|
-
# Slim::Engine > Temple::Engine
|
57
|
-
# Slim::Compiler > Temple::Filter
|
58
|
-
#
|
59
|
-
# It is recommended to set the default settings only once in the code and avoid duplication. Only use
|
60
|
-
# `set_default_options` when you have to override some default settings.
|
61
|
-
#
|
15
|
+
|
62
16
|
use Slim::Parser, :file, :tabsize, :encoding, :shortcut, :default_tag
|
63
17
|
use Slim::EmbeddedEngine, :enable_engines, :disable_engines, :pretty
|
64
18
|
use Slim::Interpolation
|
65
|
-
use Slim::Sections, :sections, :dictionary, :dictionary_access
|
66
19
|
use Slim::EndInserter
|
67
20
|
use Slim::Compiler, :disable_capture, :attr_delimiter, :attr_wrapper, :sort_attrs, :remove_empty_attrs, :default_tag
|
68
21
|
html :AttributeMerger, :attr_delimiter
|
data/lib/slim/filter.rb
CHANGED
@@ -7,6 +7,11 @@ module Slim
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class Filter < Temple::HTML::Filter
|
10
|
+
# Pass-through handler
|
11
|
+
def on_slim_text(content)
|
12
|
+
[:slim, :text, compile(content)]
|
13
|
+
end
|
14
|
+
|
10
15
|
# Pass-through handler
|
11
16
|
def on_slim_embedded(type, content)
|
12
17
|
[:slim, :embedded, code, compile(content)]
|
@@ -17,11 +22,6 @@ module Slim
|
|
17
22
|
[:slim, :control, code, compile(content)]
|
18
23
|
end
|
19
24
|
|
20
|
-
# Pass-through handler
|
21
|
-
def on_slim_condcomment(condition, content)
|
22
|
-
[:slim, :condcomment, condition, compile(content)]
|
23
|
-
end
|
24
|
-
|
25
25
|
# Pass-through handler
|
26
26
|
def on_slim_output(code, escape, content)
|
27
27
|
[:slim, :output, code, escape, compile(content)]
|
data/lib/slim/grammar.rb
CHANGED
@@ -6,10 +6,10 @@ module Slim
|
|
6
6
|
|
7
7
|
Expression <<
|
8
8
|
[:slim, :control, String, Expression] |
|
9
|
-
[:slim, :condcomment, String, Expression] |
|
10
9
|
[:slim, :output, Bool, String, Expression] |
|
11
10
|
[:slim, :interpolate, String] |
|
12
11
|
[:slim, :embedded, String, Expression] |
|
12
|
+
[:slim, :text, Expression] |
|
13
13
|
[:slim, :tag, String, SlimAttrs, 'Expression?']
|
14
14
|
|
15
15
|
SlimAttrs <<
|
data/lib/slim/interpolation.rb
CHANGED
@@ -16,9 +16,7 @@ module Slim
|
|
16
16
|
case string
|
17
17
|
when /\A\\#\{/
|
18
18
|
# Escaped interpolation
|
19
|
-
|
20
|
-
# to filter out protected strings (Issue #141).
|
21
|
-
block << [:slim, :output, false, '\'#{\'', [:multi]]
|
19
|
+
block << [:static, '#{']
|
22
20
|
string = $'
|
23
21
|
when /\A#\{/
|
24
22
|
# Interpolation
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Slim
|
2
2
|
# Handle logic-less mode
|
3
|
-
# This filter can be activated with the option "
|
3
|
+
# This filter can be activated with the option "logic_less"
|
4
4
|
# @api private
|
5
|
-
class
|
6
|
-
set_default_options :
|
7
|
-
:
|
5
|
+
class LogicLess < Filter
|
6
|
+
set_default_options :logic_less => true,
|
7
|
+
:dictionary => 'self',
|
8
8
|
:dictionary_access => :wrapped # :symbol, :string, :wrapped
|
9
9
|
|
10
10
|
def initialize(opts = {})
|
@@ -15,12 +15,12 @@ module Slim
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def call(exp)
|
18
|
-
if options[:
|
19
|
-
|
18
|
+
if options[:logic_less]
|
19
|
+
@dict = unique_name
|
20
20
|
dictionary = options[:dictionary]
|
21
|
-
dictionary = "Slim::Wrapper.new(#{dictionary})" if options[:dictionary_access] == :wrapped
|
21
|
+
dictionary = "::Slim::LogicLess::Wrapper.new(#{dictionary})" if options[:dictionary_access] == :wrapped
|
22
22
|
[:multi,
|
23
|
-
[:code, "
|
23
|
+
[:code, "#{@dict} = #{dictionary}"],
|
24
24
|
super]
|
25
25
|
else
|
26
26
|
exp
|
@@ -37,7 +37,7 @@ module Slim
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def on_slim_output(escape, name, content)
|
40
|
-
raise 'Output statements with content are forbidden in
|
40
|
+
raise 'Output statements with content are forbidden in logic less mode' if !empty_exp?(content)
|
41
41
|
[:slim, :output, escape, access(name), content]
|
42
42
|
end
|
43
43
|
|
@@ -50,11 +50,11 @@ module Slim
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def on_dynamic(code)
|
53
|
-
raise 'Embedded code is forbidden in
|
53
|
+
raise 'Embedded code is forbidden in logic less mode'
|
54
54
|
end
|
55
55
|
|
56
56
|
def on_code(code)
|
57
|
-
raise 'Embedded code is forbidden in
|
57
|
+
raise 'Embedded code is forbidden in logic less mode'
|
58
58
|
end
|
59
59
|
|
60
60
|
protected
|
@@ -77,9 +77,9 @@ module Slim
|
|
77
77
|
[:multi,
|
78
78
|
# Wrap map in array because maps implement each
|
79
79
|
[:code, "#{tmp1} = [#{tmp1}] if #{tmp1}.respond_to?(:has_key?) || !#{tmp1}.respond_to?(:map)"],
|
80
|
-
[:code, "#{tmp2} =
|
81
|
-
[:block, "#{tmp1}.each do |
|
82
|
-
[:code, "
|
80
|
+
[:code, "#{tmp2} = #{@dict}"],
|
81
|
+
[:block, "#{tmp1}.each do |#{@dict}|", content],
|
82
|
+
[:code, "#{@dict} = #{tmp2}"]]]]
|
83
83
|
end
|
84
84
|
|
85
85
|
private
|
@@ -88,9 +88,9 @@ module Slim
|
|
88
88
|
return name if name == 'yield'
|
89
89
|
case options[:dictionary_access]
|
90
90
|
when :string
|
91
|
-
"
|
91
|
+
"#{@dict}[#{name.to_s.inspect}]"
|
92
92
|
else
|
93
|
-
"
|
93
|
+
"#{@dict}[#{name.to_sym.inspect}]"
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Slim
|
2
|
+
class LogicLess
|
3
|
+
# For logic-less mode, objects can be encased in the Wrapper class.
|
4
|
+
# @api private
|
5
|
+
class Wrapper
|
6
|
+
attr_reader :value, :parent
|
7
|
+
|
8
|
+
def initialize(value, parent = nil)
|
9
|
+
@value, @parent = value, parent
|
10
|
+
end
|
11
|
+
|
12
|
+
# To find the reference, first check for standard method
|
13
|
+
# access by using respond_to?.
|
14
|
+
#
|
15
|
+
# If not found, check to see if the value is a hash and if the
|
16
|
+
# the name is a key on the hash.
|
17
|
+
#
|
18
|
+
# Not a hash, or not a key on the hash, then check to see if there
|
19
|
+
# is an instance variable with the name.
|
20
|
+
#
|
21
|
+
# If the instance variable doesn't exist and there is a parent object,
|
22
|
+
# go through the same steps on the parent object. This is useful when
|
23
|
+
# you are iterating over objects.
|
24
|
+
def [](name)
|
25
|
+
return wrap(value.send(name)) if value.respond_to?(name)
|
26
|
+
if value.respond_to?(:has_key?)
|
27
|
+
return wrap(value[name.to_sym]) if value.has_key?(name.to_sym)
|
28
|
+
return wrap(value[name.to_s]) if value.has_key?(name.to_s)
|
29
|
+
end
|
30
|
+
begin
|
31
|
+
var_name = "@#{name}"
|
32
|
+
return wrap(value.instance_variable_get(var_name)) if value.instance_variable_defined?(var_name)
|
33
|
+
rescue NameError
|
34
|
+
# Do nothing
|
35
|
+
end
|
36
|
+
parent[name] if parent
|
37
|
+
end
|
38
|
+
|
39
|
+
# Empty objects must appear empty for inverted sections
|
40
|
+
def empty?
|
41
|
+
value.respond_to?(:empty) && value.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Used for output
|
45
|
+
def to_s
|
46
|
+
value.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def wrap(response)
|
52
|
+
# Primitives are not wrapped
|
53
|
+
if [String, Numeric, TrueClass, FalseClass, NilClass].any? {|primitive| primitive === response }
|
54
|
+
response
|
55
|
+
# Enumerables are mapped with wrapped values (except Hash-like objects)
|
56
|
+
elsif !response.respond_to?(:has_key?) && response.respond_to?(:map)
|
57
|
+
response.map {|v| wrap(v) }
|
58
|
+
else
|
59
|
+
Wrapper.new(response, self)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/slim/parser.rb
CHANGED
@@ -64,7 +64,7 @@ module Slim
|
|
64
64
|
end
|
65
65
|
|
66
66
|
result = [:multi]
|
67
|
-
reset(str.split(
|
67
|
+
reset(str.split(/\r?\n/), [result])
|
68
68
|
|
69
69
|
parse_line while next_line
|
70
70
|
|
@@ -180,11 +180,11 @@ module Slim
|
|
180
180
|
# Found a comment block.
|
181
181
|
if @line =~ %r{\A/!( ?)(.*)\Z}
|
182
182
|
# HTML comment
|
183
|
-
@stacks.last << [:html, :comment, parse_text_block($2, @indents.last + $1.size + 2)]
|
183
|
+
@stacks.last << [:html, :comment, [:slim, :text, parse_text_block($2, @indents.last + $1.size + 2)]]
|
184
184
|
elsif @line =~ %r{\A/\[\s*(.*?)\s*\]\s*\Z}
|
185
185
|
# HTML conditional comment
|
186
186
|
block = [:multi]
|
187
|
-
@stacks.last << [:
|
187
|
+
@stacks.last << [:html, :condcomment, $1, block]
|
188
188
|
@stacks << block
|
189
189
|
else
|
190
190
|
# Slim comment
|
@@ -193,13 +193,13 @@ module Slim
|
|
193
193
|
when /\A([\|'])( ?)(.*)\Z/
|
194
194
|
# Found a text block.
|
195
195
|
trailing_ws = $1 == "'"
|
196
|
-
@stacks.last << parse_text_block($3, @indents.last + $2.size + 1)
|
196
|
+
@stacks.last << [:slim, :text, parse_text_block($3, @indents.last + $2.size + 1)]
|
197
197
|
@stacks.last << [:static, ' '] if trailing_ws
|
198
198
|
when /\A-/
|
199
199
|
# Found a code block.
|
200
200
|
# We expect the line to be broken or the next line to be indented.
|
201
|
-
block = [:multi]
|
202
201
|
@line.slice!(0)
|
202
|
+
block = [:multi]
|
203
203
|
@stacks.last << [:slim, :control, parse_broken_line, block]
|
204
204
|
@stacks << block
|
205
205
|
when /\A=/
|
@@ -306,10 +306,9 @@ module Slim
|
|
306
306
|
@stacks.delete_at(i)
|
307
307
|
when /\A\s*=(=?)('?)/
|
308
308
|
# Handle output code
|
309
|
-
block = [:multi]
|
310
309
|
@line = $'
|
311
|
-
|
312
|
-
tag <<
|
310
|
+
block = [:multi]
|
311
|
+
tag << [:slim, :output, $1 != '=', parse_broken_line, block]
|
313
312
|
@stacks.last << [:static, ' '] unless $2.empty?
|
314
313
|
@stacks << block
|
315
314
|
when /\A\s*\//
|
@@ -321,17 +320,16 @@ module Slim
|
|
321
320
|
@stacks << content
|
322
321
|
when /\A( ?)(.*)\Z/
|
323
322
|
# Text content
|
324
|
-
tag << parse_text_block($2, @orig_line.size - @line.size + $1.size, true)
|
323
|
+
tag << [:slim, :text, parse_text_block($2, @orig_line.size - @line.size + $1.size, true)]
|
325
324
|
end
|
326
325
|
end
|
327
326
|
|
328
327
|
def parse_attributes
|
329
328
|
attributes = [:slim, :attrs]
|
330
|
-
attribute = nil
|
331
329
|
|
332
330
|
# Find any shortcut attributes
|
333
331
|
while @line =~ @shortcut_regex
|
334
|
-
# The class/id attribute is :static instead of :slim :
|
332
|
+
# The class/id attribute is :static instead of :slim :interpolate,
|
335
333
|
# because we don't want text interpolation in .class or #id shortcut
|
336
334
|
attributes << [:html, :attr, @shortcut[$1][1], [:static, $2]]
|
337
335
|
@line = $'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'slim'
|
2
|
+
|
3
|
+
module Slim
|
4
|
+
class Translator < Filter
|
5
|
+
set_default_options :tr_mode => :dynamic,
|
6
|
+
:tr_fn => '_'
|
7
|
+
|
8
|
+
if Object.const_defined?(:I18n)
|
9
|
+
set_default_options :tr_fn => '::Slim::Translator.i18n_text',
|
10
|
+
:tr => true
|
11
|
+
elsif Object.const_defined?(:GetText)
|
12
|
+
set_default_options :tr_fn => '::GetText._',
|
13
|
+
:tr => true
|
14
|
+
elsif Object.const_defined?(:FastGettext)
|
15
|
+
set_default_options :tr_fn => '::FastGettext::Translation._',
|
16
|
+
:tr => true
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.i18n_text(text)
|
20
|
+
I18n.t!(text)
|
21
|
+
rescue I18n::MissingTranslationData
|
22
|
+
text
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.i18n_key(text)
|
26
|
+
key = text.parameterize.underscore
|
27
|
+
I18n.t!(key)
|
28
|
+
rescue I18n::MissingTranslationData
|
29
|
+
text
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(exp)
|
33
|
+
if options[:tr]
|
34
|
+
super
|
35
|
+
else
|
36
|
+
exp
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(opts)
|
41
|
+
super
|
42
|
+
case options[:tr_mode]
|
43
|
+
when :static
|
44
|
+
@translator = StaticTranslator.new(options)
|
45
|
+
when :dynamic
|
46
|
+
@translator = DynamicTranslator.new(options)
|
47
|
+
else
|
48
|
+
raise "Invalid translator mode #{options[:tr_mode].inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_slim_text(exp)
|
53
|
+
[:slim, :text, @translator.call(exp)]
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
class StaticTranslator < Filter
|
59
|
+
def initialize(opts)
|
60
|
+
super
|
61
|
+
@translate = eval("proc {|string| #{options[:tr_fn]}(string) }")
|
62
|
+
end
|
63
|
+
|
64
|
+
def call(exp)
|
65
|
+
@text, @captures = '', []
|
66
|
+
result = compile(exp)
|
67
|
+
|
68
|
+
text = @translate.call(@text)
|
69
|
+
while text =~ /%(\d+)/
|
70
|
+
result << [:static, $`] << @captures[$1.to_i - 1]
|
71
|
+
text = $'
|
72
|
+
end
|
73
|
+
result << [:static, text]
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_static(text)
|
77
|
+
@text << text
|
78
|
+
[:multi]
|
79
|
+
end
|
80
|
+
|
81
|
+
def on_slim_output(escape, code, content)
|
82
|
+
@captures << [:slim, :output, escape, code, content]
|
83
|
+
@text << "%#{@captures.size}"
|
84
|
+
[:multi]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class DynamicTranslator < Filter
|
89
|
+
def call(exp)
|
90
|
+
@captures_count, @captures_var, @text = 0, unique_name, ''
|
91
|
+
|
92
|
+
result = compile(exp)
|
93
|
+
|
94
|
+
if @captures_count > 0
|
95
|
+
result.insert(1, [:code, "#{@captures_var}=[]"])
|
96
|
+
result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect}).gsub(/%(\\d+)/) { #{@captures_var}[$1.to_i-1] }", [:multi]]
|
97
|
+
else
|
98
|
+
result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect})", [:multi]]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_static(text)
|
103
|
+
@text << text
|
104
|
+
[:multi]
|
105
|
+
end
|
106
|
+
|
107
|
+
def on_slim_output(escape, code, content)
|
108
|
+
@captures_count += 1
|
109
|
+
@text << "%#{@captures_count}"
|
110
|
+
[:capture, "#{@captures_var}[#{@captures_count-1}]", [:slim, :output, escape, code, content]]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Insert plugin filter into Slim engine chain
|
117
|
+
Slim::Engine.before(Slim::EndInserter, Slim::Translator, :tr, :tr_fn, :tr_mode)
|