diff --git a/README.md b/README.md index e4de52c4e..79c664060 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Please check the [Benchmarks and Memory Profiles Source](http://github.com/ddnex - Pagy has a very slim core code of just ~100 line of simple ruby, organized in 3 flat modules very easy to understand and use _(see [more...](https://ddnexus.github.io/pagy/api))_ - It has a quite fat set of optional extras that you can explicitly require for very efficient and modular customization _(see [extras](https://ddnexus.github.io/pagy/extras))_ -- It has no dependencies: it produces its own HTML, URLs, pluralization and interpolation with its own specialized and fast code _(see [why...](https://ddnexus.github.io/pagy/index#specialized-code-instead-of-generic-helpers))_ +- It has no dependencies: it produces its own HTML, URLs, i18n with its own specialized and fast code _(see [why...](https://ddnexus.github.io/pagy/index#specialized-code-instead-of-generic-helpers))_ - 100% of its methods are public API, accessible and overridable **right where you use them** (no need of monkey-patching) - 100% test coverage for core code and extras diff --git a/docs/api/frontend.md b/docs/api/frontend.md index 57a133cd9..94a04d805 100644 --- a/docs/api/frontend.md +++ b/docs/api/frontend.md @@ -160,84 +160,73 @@ This method is similar to the `I18n.t` and its equivalent rails `t` helper. It i **IMPORTANT**: if you are using pagy with some language missing from the [dictionary files](https://github.com/ddnexus/pagy/blob/master/lib/locales), please, submit your translation! -Pagy is I18n ready. That means that all its strings are stored in a dictionary file of one of its [languages](https://github.com/ddnexus/pagy/blob/master/lib/locales), ready to be customized and/or translated/pluralized and used with or without the `I18n` gem. +Pagy is i18n ready. That means that all its strings are stored in the dictionary files of its [locales](https://github.com/ddnexus/pagy/blob/master/lib/locales), ready to be customized and/or used with or without the `I18n` gem. -A Pagy dictionary file is a YAML file containing 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). The file follows the same structure of the standard locale files for `i18n`. +**Notice**: a Pagy dictionary file is a YAML file containing a few entries used internally in the the UI by helpers and templates through the [pagy_t](#pagy_tpath-vars) method. The file follows the same structure of the standard locale files for the `i18n` gem. -### Multi-language apps +### Pagy I18n implementation -For multi-language apps you need the dynamic translation provided by the [i18n extra](../extras/i18n.md), which delegates the handling of the pagy strings to the `I18n` gem. In that case you need only to require the I18n extra in the initializer file. +The pagy internal i18n implementation is ~12x faster and uses ~6x less memory than the standard `i18n` gem. -**Notice**: For simplicity, you could also use the `i18n` extra for single-language apps, but if you want more performance, please follow the specific documentation below. +Since Pagy version 2.0, you can use it for both single-language and multi-language apps, with or without the `i18n` gem. -### Single-language apps +Notice: if your app is using i18n, it will work independently from it. -Single-language apps (i.e. only `fr` or only `en` or only ...) don't need to switch between languages, so they don't need the `i18n` extra/`I18n` gem (although you could choose to use it). +The pagy internal i18n is implemented around the `Pagy::I18n` constant hash which contains the locales data needed to pagy and your app. You may need to configure it in the [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) initializer. -By default, Pagy handles its own dictionary file directly, providing pluralization and interpolation (without dynamic translation) _5x faster_ and using _3.5x less memory_ than the standard `I18n` gem. +#### Pagy::I18n.load configuration -If you are fine with the locales provided with pagy, you just need to load the dictionary file of your language by adding this line the initializer file. For example with `zh-cn`: +By default pagy will render its output using the built-in `en` locale. If your app uses only `en` and you are fine with the built-in strings, you are done without configuring anything at all. -```ruby -Pagy::Frontend::I18N.load(file: Pagy.root.join('locales', 'zh-cn.yml'), language:'zh-cn') -``` - -If you need to use your own translation file and/or customize the Pagy strings in this scenario, you may need the following steps: - -1. copy and edit one of the [dictionary files](https://github.com/ddnexus/pagy/blob/master/lib/locales) -2. load it in the initializer file (e.g. `Pagy::Frontend::I18N.load(file:..., language:'tr')` -3. see [Adding the model translations](#adding-the-model-translations) below -4. check if you need to configure some of the following variables in the [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) initializer. - -#### Pagy::Frontend::I18N Constant +If you need to load different built-in locales, and/or custom dictionary files or even non built-in languages and pluralizations, you can do it all by passing a few arguments to the `Pagy::I18n.load` method. -**IMPORTANT**: This variable has no effect if you use the `i18n` extra. +**Notice**: the `Pagy::I18n.load` method is intended to be used once in the [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) initializer. If you use it multiple times, the last statement will override the previous statements. -The `Pagy::Frontend::I18N` constant is the core of the Pagy I18n implementation. This constant allows to control the dictionary file, the language to load and the pluralization proc. +Here are a few examples that should cover all the possible confgurations: -#### Pagy::Frontend::I18N.load(file:..., language:'en') +```rb +# IMPORTANT: use only one load statement -**IMPORTANT**: This method has no effect if you use the `i18n` extra. +# load the "de" built-in locale: +Pagy::I18n.load(locale: 'de') -It 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')`. It is tipically used in the Pagy initializer file _(see [Configuration](../how-to.md#global-configuration))_. For example: +# load the "de" locale defined in the custom file at :filepath: +Pagy::I18n.load(locale: 'de', filepath: 'path/to/pagy-de.yml') -```ruby -# 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') +# load the "de", "en" and "es" built-in locales: +# the first :locale will be used also as the default_locale +Pagy::I18n.load({locale: 'de'}, + {locale: 'en'}, + {locale: 'es'}) + +# load the "en" built-in locale, a custom "es" locale, and a totally custom locale complete with the :pluralize proc: +Pagy::I18n.load({locale: 'en'}, + {locale: 'es', filepath: 'path/to/pagy-es.yml'}, + {locale: 'xyz', # not built-in + filepath: 'path/to/pagy-xyz.yml', + pluralize: lambda{|count| ... } ) ``` -**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[:plural] +**Notice**: You should use a custom `:pluralize` proc only for pluralization types not included in the built-in [p11n.rb](https://github.com/ddnexus/pagy/blob/master/lib/locales/utils/p11n.rb) + rules. In that case, please submit a PR with your dictionary file and plural rule. The `:pluralize` 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). -**IMPORTANT**: This variable has no effect if you use the `i18n` extra. +#### Set the request locale in multi-language apps -This variable controls the internal pluralization. +When you configure multiple locales, you must also set the locale for each request. You usually do that in the application controller, by checking the `:locale` param. For example, in a rails app you should do something like: -Pagy tries to set the language plural proc when you use the `Pagy::Frontend::I18N.load` method, by loading the built-in plural for the language. _(see [plurals.rb](https://github.com/ddnexus/pagy/blob/master/lib/locales/plurals.rb))_ - -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). - -If your custom language requires a pluralization different than `:zero_one_other`, you should define a custom rule. For example: - -```ruby -# this would apply a custom pluralization rule to the current loaded dictionary -Pagy::Frontend::I18N[:plural] = -> (count) {|count| ...} +```rb +before_action { @pagy_locale = params[:locale] || 'en' } ``` -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. +That instance variable will be used by the [pagy_t](#pagy_tpath-vars) method included in your view and will translate the pagy strings to the selected locale. + +**Notice**: In case of `@pagy_locale.nil?` or unknown/not-loaded, then the first loaded locale will be used for the translation. That means that you don't have to set the `@pagy_locale` variable if your app uses just a single locale. #### Adding the model translations -When Pagy uses its own handling of the dictionary file, it has only access to the strings in its own file and not in other `I18n` files used by the rest of the app. +When Pagy uses its own i18n implementation, it has only access to the strings in its own files and not in other `I18n` files used by the rest of the app. -That means that if you use the `pagy_info` helper with the specific model names instead of the generic "items" string, you may need to add entries for the models in the pagy dictionary file. For example: +That means that if you use the `pagy_info` helper with the specific model names instead of the generic "items" string, you may need to add entries for the models in the pagy dictionary files. For example: ```yaml en: @@ -255,3 +244,10 @@ en: ``` _(See also the [pagy_info method](#pagy_infopagy))_ + + +### Using the I18n gem + +If - despite the disadvantages - you want to use the standard `i18n` gem in place of the pagy i18n implementation, you should use the [i18n extra](../extras/i18n.md), which delegates the handling of the pagy strings to the `i18n` gem. In that case you need only to require the extra in the initializer file with `require 'pagy/extras/i18n'` and everything will be handled by the `i18n` gem. + + **Notice**: if you use the [i18n extra](../extras/i18n.md)/`i18n` gem, you don't need any of the above configurations. diff --git a/docs/extras/i18n.md b/docs/extras/i18n.md index 5ef11970e..c12c3fcd6 100644 --- a/docs/extras/i18n.md +++ b/docs/extras/i18n.md @@ -3,7 +3,11 @@ title: I18n --- # I18n Extra -The `i18n` extra overrides the `pagy_t` method so it uses `I18n.t`. Use this extra only with multi-language apps since the `I18n` gem adds quite an overhead and slowers down Pagy. +**Notice**: Since Pagy version 2.0, you can use the pagy `i18n` implementation for both single-language and multi-language apps, with or without the `i18n` gem. + +The `i18n` extra overrides the `pagy_t` method so it uses `I18n.t` implemented by the `i18n` gem. + +The `I18n.t` is ~12x slower and uses ~6x more memory than `pagy_t` so use it wisely. See also [I18n](../api/frontend.md#i18n). diff --git a/docs/how-to.md b/docs/how-to.md index 190c3bfaf..fe29b37b6 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -327,7 +327,7 @@ By default Pagy generates all the page links including the `page` param. If you ## Using Templates -The `pagy_nav*` helpers are optimized for speed, and they are really fast. On the other hand editing a template might be easier when you have to customize the rendering, however every template system adds some inevitable overhead and it will be about 40-80% slower than using the related helper. That will still be dozens of times faster than the other gems, but... you should choose wisely. +The `pagy_nav*` helpers are optimized for speed, and they are really fast. On the other hand editing a template might be easier when you have to customize the rendering, however every template system adds some inevitable overhead and it will be about 30-70% slower than using the related helper. That will still be dozens of times faster than the other gems, but... you should choose wisely. Pagy provides the replacement templates for the `pagy_nav`, `pagy_bootstrap_nav`, `pagy_bulma_nav` and the `pagy_foundation_nav` helpers (available with the relative extras) in 3 flavors: `erb`, `haml` and `slim`. diff --git a/lib/config/pagy.rb b/lib/config/pagy.rb index 1172ecd68..7b12fc38e 100644 --- a/lib/config/pagy.rb +++ b/lib/config/pagy.rb @@ -1,5 +1,5 @@ # Pagy initializer file -# Customize only what you really need but notice that Pagy works also without any of the following lines. +# Customize only what you really need and notice that Pagy works also without any of the following lines. # Extras @@ -111,15 +111,34 @@ # I18n -# I18n faster internal pagy implementation (does not use the I18n gem) -# Use only for single language apps that don't need dynamic translation between multiple languages +# Pagy internal I18n: ~12x faster using ~6x less memory than the i18n gem # See https://ddnexus.github.io/pagy/api/frontend#i18n -# Notice: Do not use any of the following lines if you use the i18n extra below -# Pagy::Frontend::I18N.load(file: Pagy.root.join('locale', 'es.yml'), language:'es') # load 'es' pagy language file -# Pagy::Frontend::I18N.load(file:'path/to/dictionary.yml', language:'en') # load a custom 'en' file -# Pagy::Frontend::I18N[:plural] = -> (count) {(['zero', 'one'][count] || 'other')} # default - -# I18n extra: Use the `I18n` gem instead of the pagy implementation -# (slower but allows dynamic translation between multiple languages) +# Notice: No need to use any of the following lines if you use the i18n extra below +# +# Examples: +# load the "de" built-in locale: +# Pagy::I18n.load(locale: 'de') +# +# load the "de" locale defined in the custom file at :filepath: +# Pagy::I18n.load(locale: 'de', filepath: 'path/to/pagy-de.yml') +# +# load the "de", "en" and "es" built-in locales: +# (the first passed :locale will be used also as the default_locale) +# Pagy::I18n.load({locale: 'de'}, +# {locale: 'en'}, +# {locale: 'es'}) +# +# load the "en" built-in locale, a custom "es" locale, +# and a totally custom locale complete with the :pluralize proc: +# (the first passed :locale will be used also as the default_locale) +# Pagy::I18n.load({locale: 'en'}, +# {locale: 'es', filepath: 'path/to/pagy-es.yml'}, +# {locale: 'xyz', # not built-in +# filepath: 'path/to/pagy-xyz.yml', +# pluralize: lambda{|count| ... } ) + + +# I18n extra: uses the standard i18n gem which is ~12x slower using ~6x more memory +# than the default pagy internal i18n (see above) # See https://ddnexus.github.io/pagy/extras/i18n # require 'pagy/extras/i18n' diff --git a/lib/locales/utils/i18n.rb b/lib/locales/utils/i18n.rb new file mode 100644 index 000000000..242b330a7 --- /dev/null +++ b/lib/locales/utils/i18n.rb @@ -0,0 +1,17 @@ +# See https://ddnexus.github.io/pagy/api/frontend#i18n +# frozen_string_literal: true + +# this file returns the I18n hash used as default alternative to the i18n gem + +Hash.new{|h,_| h.first[1]}.tap do |i18n| # first loaded locale used as default + i18n.define_singleton_method(:load) do |*args| + # eval: we don't need to keep the loader proc in memory + eval(Pagy.root.join('locales', 'utils', 'loader.rb').read).call(i18n, *args) #rubocop:disable Security/Eval + end + i18n.define_singleton_method(:t) do |locale, path, vars={}| + data, pluralize = self[locale] + translate = data[path] || vars[:count] && data[path+=".#{pluralize.call(vars[:count])}"] or return %([translation missing: "#{path}"]) + translate.call(vars) + end + i18n.load(locale: 'en') +end diff --git a/lib/locales/utils/loader.rb b/lib/locales/utils/loader.rb new file mode 100644 index 000000000..01180de1c --- /dev/null +++ b/lib/locales/utils/loader.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# the whole file will be eval'ed/executed and gc-collected after returning/executing the loader proc + +# eval: no need for the whole file in memory +p11n = eval(Pagy.root.join('locales', 'utils', 'p11n.rb').read) #rubocop:disable Security/Eval + +# flatten the dictionary file nested keys +# convert each value to a simple ruby interpolation proc +flatten = lambda do |hash, key=''| + hash.each.reduce({}) do |h, (k, v)| + v.is_a?(Hash) \ + ? h.merge!(flatten.call(v, "#{key}#{k}.")) + : h.merge!(eval %({"#{key}#{k}" => lambda{|vars|"#{v.gsub(/%{[^}]+?}/){|m| "\#{vars[:#{m[2..-2]}]||'#{m}'}" }}"}})) #rubocop:disable Security/Eval + end + end + +# loader proc +lambda do |i18n, *args| + i18n.clear + args.each do |arg| + arg[:filepath] ||= Pagy.root.join('locales', "#{arg[:locale]}.yml") + arg[:pluralize] ||= p11n[arg[:locale]] + hash = YAML.load(File.read(arg[:filepath], encoding: 'UTF-8')) #rubocop:disable Security/YAMLLoad + hash.key?(arg[:locale]) or raise ArgumentError, %(Pagy::I18n.load: :locale "#{arg[:locale]}" not found in :filepath "#{arg[:filepath].inspect}") + i18n[arg[:locale]] = [flatten.call(hash[arg[:locale]]), arg[:pluralize]] + end +end diff --git a/lib/locales/plurals.rb b/lib/locales/utils/p11n.rb similarity index 66% rename from lib/locales/plurals.rb rename to lib/locales/utils/p11n.rb index 0ac24ed06..6f92f8d5c 100644 --- a/lib/locales/plurals.rb +++ b/lib/locales/utils/p11n.rb @@ -1,7 +1,9 @@ -# This file adds support for multiple built-in plualization types. -# It defines the pluralization procs and gets eval(ed) at I18N.load time. +# See https://ddnexus.github.io/pagy/api/frontend#i18n # frozen_string_literal: true +# This file adds support for multiple built-in plualization types. +# It defines the pluralization procs and gets eval(ed) and gc-collected at Pagy::I18n.load time. + # utility variables zero_one = ['zero', 'one'].freeze from2to4 = (2..4).freeze @@ -9,10 +11,10 @@ from11to14 = (11..14).freeze from12to14 = (12..14).freeze -# Plurals -# A plural proc returns a plural type string based on the passed count -# Each plural proc may apply to one or more languages below -plurals = { +# Pluralization (p11n) +# A pluralization proc returns a plural type string based on the passed count +# Each proc may apply to one or more locales below +p11n = { zero_one_other: lambda {|count| zero_one[count] || 'other'}, zero_one_few_many_other: lambda do |count| @@ -36,14 +38,13 @@ end } -# Languages (language/plural pairs) -# Contain all the entries for all the languages defined in the dictionaries. -# The default plural for languages not explicitly listed here -# is the :zero_one_other plural (used for English) -Hash.new(plurals[:zero_one_other]).tap do |languages| - languages['en'] = plurals[:zero_one_other] - languages['ru'] = plurals[:zero_one_few_many_other] - languages['pl'] = plurals[:pl] +# Hash of locale/pluralization pairs +# Contain all the entries for all the locales defined as dictionaries. +# The default pluralization for locales not explicitly listed here +# is the :zero_one_other pluralization proc (used for English) +Hash.new(p11n[:zero_one_other]).tap do |hash| + hash['ru'] = p11n[:zero_one_few_many_other] + hash['pl'] = p11n[:pl] end -# PR for other languages and plurals are very welcome. Thanks! +# PR for other locales and pluralizations are very welcome. Thanks! diff --git a/lib/pagy/extras/i18n.rb b/lib/pagy/extras/i18n.rb index a8b487964..48d2ce759 100644 --- a/lib/pagy/extras/i18n.rb +++ b/lib/pagy/extras/i18n.rb @@ -1,4 +1,5 @@ # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/i18n +# frozen_string_literal: true class Pagy # Use ::I18n gem @@ -6,10 +7,11 @@ module Frontend ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')] - # Override the built-in pagy_t - def pagy_t(*args) - ::I18n.t(*args) - end + Pagy::I18n.clear.instance_eval { undef :load; undef :t } # unload the pagy default constant for efficiency + + # no :pagy_without_i18n alias with the i18n gem + def pagy_t_with_i18n(*args) ::I18n.t(*args) end + alias :pagy_t :pagy_t_with_i18n end end diff --git a/lib/pagy/frontend.rb b/lib/pagy/frontend.rb index ff52d3d21..f1a8b5c14 100644 --- a/lib/pagy/frontend.rb +++ b/lib/pagy/frontend.rb @@ -5,6 +5,10 @@ class Pagy + # I18n static hash loaded at startup, used as default alternative to the i18n gem. + # see https://ddnexus.github.io/pagy/api/frontend#i18n + I18n = eval(Pagy.root.join('locales', 'utils', 'i18n.rb').read) #rubocop:disable Security/Eval + # All the code here has been optimized for performance: it may not look very pretty # (as most code dealing with many long strings), but its performance makes it very sexy! ;) module Frontend @@ -57,25 +61,8 @@ def pagy_link_proc(pagy, link_extra='') else '' end } #{extra}>#{text}" } end - # Pagy::Frontend::I18N - def (I18N = {data:{}}).load(file:Pagy.root.join('locales', 'en.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 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.split('.')) or return %(translation missing: "#{path}") - if value.is_a?(Hash) - vars.key?(:count) or return value - 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 - sprintf value, Hash.new{|_,k| "%{#{k}}"}.merge!(vars) # interpolation - end + # Similar to I18n.t: just ~12x faster using ~6x less memory + def pagy_t(path, vars={}) Pagy::I18n.t(@pagy_locale, path, vars) end end end diff --git a/test/pagy/custom.yml b/test/pagy/custom.yml new file mode 100644 index 000000000..b05f194cb --- /dev/null +++ b/test/pagy/custom.yml @@ -0,0 +1,20 @@ +custom: + pagy: + nav: + prev: "‹ Custom Prev" # test + next: "Next ›" + gap: "…" + info: + single_page: + zero: "No %{item_name} found" + one: "Displaying 1 %{item_name}" + other: "Displaying all %{count} %{item_name}" + multiple_pages: "Displaying %{item_name} %{from}-%{to} of %{count} in total" + item_name: + zero: "items" + one: "item" + other: "items" + compact: "Page %{page_input} of %{pages}" + items: + one: "Show %{items_input} item per page" + other: "Show %{items_input} items per page" diff --git a/test/pagy/extras/i18n_test.rb b/test/pagy/extras/i18n_test.rb index d641c9d3e..a60f19898 100644 --- a/test/pagy/extras/i18n_test.rb +++ b/test/pagy/extras/i18n_test.rb @@ -10,11 +10,6 @@ describe "#pagy_t with I18n" do - it 'fetches data' do - Pagy::Frontend::I18N[:data]['pagy']['nav']['prev'].must_equal "‹ Prev" - Pagy::Frontend::I18N[:data]['pagy']['nav']['gap'].must_equal "…" - end - it 'pluralizes' do frontend.pagy_t('pagy.nav.prev').must_equal "‹ Prev" frontend.pagy_t('pagy.info.item_name', count: 0).must_equal 'items' diff --git a/test/pagy/frontend_test.rb b/test/pagy/frontend_test.rb index 2bf069282..dfbd931cf 100644 --- a/test/pagy/frontend_test.rb +++ b/test/pagy/frontend_test.rb @@ -99,17 +99,11 @@ describe "#pagy_t" do - it 'fetches I18N data' do - Pagy::Frontend::I18N[:data]['pagy']['nav']['prev'].must_equal "‹ Prev" - Pagy::Frontend::I18N[:data]['pagy']['nav']['gap'].must_equal "…" - end - it 'pluralizes' do frontend.pagy_t('pagy.nav.prev').must_equal "‹ Prev" frontend.pagy_t('pagy.info.item_name', count: 0).must_equal "items" frontend.pagy_t('pagy.info.item_name', count: 1).must_equal "item" frontend.pagy_t('pagy.info.item_name', count: 10).must_equal "items" - end it 'interpolates' do @@ -117,10 +111,46 @@ frontend.pagy_t('pagy.info.single_page', count: 1).must_equal "Displaying 1 %{item_name}" frontend.pagy_t('pagy.info.single_page', count: 10).must_equal "Displaying all 10 %{item_name}" frontend.pagy_t('pagy.info.multiple_pages', count: 10).must_equal "Displaying %{item_name} %{from}-%{to} of 10 in total" + frontend.pagy_t('pagy.info.multiple_pages', item_name: 'Products', count: 300, from: 101, to: 125).must_equal "Displaying Products 101-125 of 300 in total" end it 'handles missing paths' do - frontend.pagy_t('pagy.nav.not_here').must_equal 'translation missing: "pagy.nav.not_here"' + frontend.pagy_t('pagy.nav.not_here').must_equal '[translation missing: "pagy.nav.not_here"]' + frontend.pagy_t('pagy.nav.gap.not_here').must_equal '[translation missing: "pagy.nav.gap.not_here"]' + end + + + end + + describe "Pagy::I18n" do + + it 'loads custom :locale, :filepath and :pluralize' do + proc{ Pagy::I18n.load(locale: 'xx') }.must_raise Errno::ENOENT + proc{ Pagy::I18n.load(locale: 'xx', filepath: Pagy.root.join('locales', 'en.yml'))}.must_raise ArgumentError + proc{ Pagy::I18n.load(locale: 'en', filepath: Pagy.root.join('locales', 'xx.yml')) }.must_raise Errno::ENOENT + custom_dictionary = File.join(File.dirname(__FILE__), 'custom.yml') + Pagy::I18n.load(locale: 'custom', filepath: custom_dictionary) + Pagy::I18n.t('custom', 'pagy.nav.prev').must_equal "‹ Custom Prev" + Pagy::I18n.load(locale: 'en', pluralize: lambda{|_| 'one' }) # returns always 'one' regardless the count + Pagy::I18n.t(nil, 'pagy.info.item_name', count: 0).must_equal "item" + Pagy::I18n.t('en', 'pagy.info.item_name', count: 0).must_equal "item" + Pagy::I18n.t('en', 'pagy.info.item_name', count: 1).must_equal "item" + Pagy::I18n.t('en', 'pagy.info.item_name', count: 10).must_equal "item" + Pagy::I18n.load(locale: 'en') # reset for other tests + end + + it 'switches :locale according to @pagy_locale' do + Pagy::I18n.load({locale: 'de'}, {locale: 'en'}, {locale: 'nl'}) + frontend.instance_variable_set(:'@pagy_locale', 'nl') + frontend.pagy_t('pagy.info.item_name', count: 1).must_equal "stuk" + frontend.instance_variable_set(:'@pagy_locale', 'en') + frontend.pagy_t('pagy.info.item_name', count: 1).must_equal "item" + frontend.instance_variable_set(:'@pagy_locale', nil) + frontend.pagy_t('pagy.info.item_name', count: 1).must_equal "Eintrag" + frontend.instance_variable_set(:'@pagy_locale', 'unknown') + frontend.pagy_t('pagy.info.item_name', count: 1).must_equal "Eintrag" # silently serves the first loaded locale + Pagy::I18n.load(locale: 'en') # reset for other tests + frontend.instance_variable_set(:'@pagy_locale', nil) # reset for other tests end end @@ -135,13 +165,14 @@ end it 'renders with existing i18n path' do - Pagy::Frontend::I18N[:data]['pagy']['info']['product'] = { 'zero' => 'Products', - 'one' => 'Product', - 'other' => 'Products' } + Pagy::I18n['en'][0]['pagy.info.product.zero'] = lambda{|_| 'Products'} + Pagy::I18n['en'][0]['pagy.info.product.one'] = lambda{|_| 'Product'} + Pagy::I18n['en'][0]['pagy.info.product.other'] = lambda{|_| 'Products'} frontend.pagy_info(Pagy.new count: 0, item_path: 'pagy.info.product').must_equal "No Products found" frontend.pagy_info(Pagy.new count: 1, item_path: 'pagy.info.product').must_equal "Displaying 1 Product" frontend.pagy_info(Pagy.new count: 13, item_path: 'pagy.info.product').must_equal "Displaying all 13 Products" frontend.pagy_info(Pagy.new count: 100, item_path: 'pagy.info.product', page: 3).must_equal "Displaying Products 41-60 of 100 in total" + Pagy::I18n.load(locale: 'en') # reset for other tests end end