Skip to content

Commit

Permalink
refactoring of I18N constant: added support for multiple static langu…
Browse files Browse the repository at this point in the history
…ages and plural rules
  • Loading branch information
ddnexus committed Jun 27, 2018
1 parent b78b71e commit 826798b
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 21 deletions.
33 changes: 27 additions & 6 deletions docs/api/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,45 @@ Pagy is I18n ready. That means that all the UI strings that Pagy uses are stored

The YAML file is available at `Pagy.root.join('locales', 'pagy.yml')`. It contains a few entries used in the the UI by helpers and templates through the [pagy_t method](#pagy_tpath-vars) (eqivalent to the `I18n.t` or rails `t` helper).

By default, the `pagy_t` method uses the Pagy implementation of I18n, which does not depend on the `I18n` gem in any way. It's _5x faster_ and uses _3.5x less memory_, but it provides only pluralization/interpolation without translation, so it's only useful with single language apps (i.e. only `fr` or only `en` or only ...)
By default, the `pagy_t` method uses the Pagy implementation of I18n, which does not depend on the `I18n` gem in any way. It's _5x faster_ and uses _3.5x less memory_, but it provides only pluralization/interpolation without dynamic translation, so it's only useful with single language apps (i.e. only `fr` or only `en` or only ...) that don't need to switch between languages.
If you need full blown I18n, you should require the `i18n` extra, which will override the `pagy_t` method to use directly `::I18n.t`.
### Pagy::Frontend::I18N Constant
The `Pagy::Frontend::I18N` constant is the core of the Pagy I18n implementation. It has no effect if you use the `i18n` extra (which uses the `I18n.t` method directly). This constant allows to control the dictionary file to load and the pluralization proc.
The `Pagy::Frontend::I18N` constant is the core of the Pagy I18n implementation. It has no effect if you use the `i18n` extra (which uses the `I18n.t` method directly). This constant allows to control the dictionary file, the language to load and the pluralization proc.
#### Pagy::Frontend::I18N.load_file(file)
#### Pagy::Frontend::I18N.load(file:..., language:'en')
This method allow to load a custom dictionary file, different from `Pagy.root.join('locales', 'pagy.yml')`. If the `i18n` extra is used it has no effect. It is tipically used in the Pagy initializer file _(see [Configuration](../how-to.md#global-configuration))_. For example:
This method allows to load a built-in language (different than the default 'en') and/or a custom dictionary file, different from `Pagy.root.join('locales', 'pagy.yml')`. If the `i18n` extra is used it has no effect. It is tipically used in the Pagy initializer file _(see [Configuration](../how-to.md#global-configuration))_. For example:
```ruby
Pagy::Frontend::I18N.load_file('path/to/dictionary.yml')
# this would load the Italian variant of the built-in dictionary
Pagy::Frontend::I18N.load(language:'it')
# this would load the default English variant of 'path/to/dictionary.yml'
Pagy::Frontend::I18N.load(file:'path/to/dictionary.yml')
# this would load the Italian variant of 'path/to/dictionary.yml'
Pagy::Frontend::I18N.load(file:'path/to/dictionary.yml', language:'it')
```
**Notice**: the Pagy implementation of I18n is designed to speedup single language apps and does not provide dynamic translation, so the `language` is statically loaded at startup-time and cannot be changed. Use the `i18n` extra if you need dynamic translation.
#### Pagy::Frontend::I18N[:plurals]
This variable controls the internal pluralization. If the `i18n` extra is used it has no effect.
By default the variable is set to a proc that receives the `count` as a single argument and returns the plural type string (e.g. something like `'zero'`, `'one'` or `'other'`, depending on the passed count). You should customize it only for pluralization types different than English.
Pagy tries to set the language plural rule proc when you use the `Pagy::Frontend::I18N.load` method, by loading the built-in plural rules.
If there is no rule defined for the language loaded, the variable is set to the `:zero_one_other` plural rule (default for English language).
You may want to define a custom rule for your custom language. For example:
```ruby
# this would apply a custom pluralization rule to the current loaded dictionary
Pagy::Frontend::I18N[:plural] = -> (count) {|count| ...}
```
The custom proc should receive the `count` as a single argument and should return the plural type string (e.g. something like `'zero'`, `'one'` or `'other'`, depending on the passed count). You should customize it only for pluralization types not included in the built-in plural rules. In that case, please submit a PR with your dictionary file and plural rule. Thanks.
2 changes: 2 additions & 0 deletions lib/locales/pagy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ en:
items:
show: "Show"
items: "items per page"

# PR for other languages are very welcome. Thanks!
25 changes: 25 additions & 0 deletions lib/locales/plurals.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This file adds support for multiple built-in plualiztion types.
# It defines the pluralization procs and gets eval(ed) at I18N.load time
# frozen_string_literal: true

# utility variables
zero_one = ['zero', 'one']

# Rules
# A rule is a proc returning a plural type string based on the passed count
# Each plural rule may apply to many language/rule pairs below
rules = {
zero_one_other: -> (count) {zero_one[count] || 'other'}
}

# Plurals (language/rule pairs)
# The default rule for missing languages is the :zero_one_other rule (used for English)
plurals = Hash.new(rules[:zero_one_other])

# Entries for all the languages defined in the pagy.yml dictionary
plurals['en'] = rules[:zero_one_other]

# PR for other languages are very welcome. Thanks!

# Return plurals
plurals
2 changes: 1 addition & 1 deletion lib/pagy/extras/i18n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Frontend

# Override the built-in pagy_t
def pagy_t(*args)
I18n.t(*args)
::I18n.t(*args)
end

end
Expand Down
19 changes: 10 additions & 9 deletions lib/pagy/frontend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,20 @@ def pagy_link_proc(pagy, link_extra='')
else '' end } #{extra}>#{text}</a>" }
end

# Pagy::Frontend::I18N constant
I18N_DATA = YAML.load_file(Pagy.root.join('locales', 'pagy.yml')).first[1]
zero_one = ['zero', 'one']; I18N = { plurals: -> (c) {zero_one[c] || 'other'}}
def I18N.load_file(file) I18N_DATA.replace(YAML.load_file(file).first[1]) end
# Pagy::Frontend::I18N
def (I18N = {data:{}}).load(file:Pagy.root.join('locales', 'pagy.yml'), language: 'en')
self[:data] = YAML.load_file(file)[language]
self[:plural] = eval(Pagy.root.join('locales', 'plurals.rb').read)[language] #rubocop:disable Security/Eval
end; I18N.load

# Similar to I18n.t for interpolation and pluralization but without translation
# Use only for single-language apps: it is specialized for Pagy and 5x faster than I18n.t
# See also https://ddnexus.github.io/pagy/extras/i18n to use the standard I18n gem instead
# Similar to I18n.t for streamlined interpolation and pluralization but without dynamic translation.
# It is specialized for Pagy and 5x faster than I18n.t (see https://ddnexus.github.io/pagy/api/frontend#i18n)
# See also https://ddnexus.github.io/pagy/extras/i18n if you need to use the standard I18n gem instead
def pagy_t(path, vars={})
value = I18N_DATA.dig(*path.to_s.split('.')) or return %(translation missing: "#{path}")
value = I18N[:data].dig(*path.split('.')) or return %(translation missing: "#{path}")
if value.is_a?(Hash)
vars.key?(:count) or return value
plural = I18N[:plurals].call(vars[:count])
plural = I18N[:plural].call(vars[:count])
value.key?(plural) or return %(invalid pluralization data: "#{path}" cannot be used with count: #{vars[:count]}; key "#{plural}" is missing.)
value = value[plural] or return %(translation missing: "#{path}")
end
Expand Down
4 changes: 2 additions & 2 deletions test/pagy/extras/i18n_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

describe "#pagy_t" do
def test_data
assert_equal "&lsaquo;&nbsp;Prev", Pagy::Frontend::I18N_DATA['pagy']['nav']['prev']
assert_equal "&hellip;", Pagy::Frontend::I18N_DATA['pagy']['nav']['gap']
assert_equal "&lsaquo;&nbsp;Prev", Pagy::Frontend::I18N[:data]['pagy']['nav']['prev']
assert_equal "&hellip;", Pagy::Frontend::I18N[:data]['pagy']['nav']['gap']
end

def test_translation
Expand Down
6 changes: 3 additions & 3 deletions test/pagy/frontend_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def test_link_extras

describe "#pagy_t" do
def test_data
assert_equal "&lsaquo;&nbsp;Prev", Pagy::Frontend::I18N_DATA['pagy']['nav']['prev']
assert_equal "&hellip;", Pagy::Frontend::I18N_DATA['pagy']['nav']['gap']
assert_equal "&lsaquo;&nbsp;Prev", Pagy::Frontend::I18N[:data]['pagy']['nav']['prev']
assert_equal "&hellip;", Pagy::Frontend::I18N[:data]['pagy']['nav']['gap']
end

def test_translation
Expand Down Expand Up @@ -147,7 +147,7 @@ def test_render_info_no_118n_key
end

def test_render_info_with_existing_118n_key
Pagy::Frontend::I18N_DATA['pagy']['info']['product'] = { 'zero' => 'Products',
Pagy::Frontend::I18N[:data]['pagy']['info']['product'] = { 'zero' => 'Products',
'one' => 'Product',
'other' => 'Products' }
pagy = Pagy.new count: 0, item_path: 'pagy.info.product'
Expand Down

0 comments on commit 826798b

Please sign in to comment.