i18n_multitenant 0.0.0 → 0.0.1

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -77
  3. data/lib/i18n_multitenant/railtie.rb +1 -2
  4. data/lib/i18n_multitenant/version.rb +1 -1
  5. data/lib/i18n_multitenant.rb +38 -2
  6. data/spec/lib/i18n_multitenant_spec.rb +174 -0
  7. data/spec/spec_helper.rb +7 -0
  8. data/spec/support/config/locales/devise.en.yml +4 -0
  9. data/spec/support/config/locales/devise.es.yml +4 -0
  10. data/spec/support/config/locales/en-GB.yml +2 -0
  11. data/spec/support/config/locales/en.yml +3 -0
  12. data/spec/support/config/locales/es.yml +2 -0
  13. data/spec/support/config/locales/simple_form.en.yml +5 -0
  14. data/spec/support/config/locales/simple_form.es.yml +5 -0
  15. data/spec/support/config/locales/tenants/enterprise/en-ENTERPRISE.yml +2 -0
  16. data/spec/support/config/locales/tenants/enterprise/es-ENTERPRISE.yml +2 -0
  17. data/spec/support/config/locales/tenants/enterprise/simple_form.es-ENTERPRISE.yml +5 -0
  18. data/spec/support/config/locales/tenants/free/devise.es-FREE.yml +4 -0
  19. data/spec/support/config/locales/tenants/free/en-FREE.yml +2 -0
  20. data/spec/support/config/locales/tenants/free/en-US-FREE.yml +2 -0
  21. data/spec/support/config/locales/tenants/free/es-FREE.yml +2 -0
  22. metadata +35 -30
  23. data/lib/i18n_multitenant/backend.rb +0 -134
  24. data/spec/fixtures/de.yml +0 -4
  25. data/spec/fixtures/de_formal.yml +0 -3
  26. data/spec/fixtures/devise.de.yml +0 -4
  27. data/spec/fixtures/devise.de_formal.yml +0 -4
  28. data/spec/fixtures/en.yml +0 -2
  29. data/spec/fixtures/tenants/another_tenant_name/another_tenant_name_fr_formal.yml +0 -2
  30. data/spec/fixtures/tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml +0 -3
  31. data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml +0 -3
  32. data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml +0 -2
  33. data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_en.yml +0 -2
  34. data/spec/lib/i18n_multitenant/backend_spec.rb +0 -204
  35. data/spec/support/shared_contexts/all_locale_file_constellations.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb35d5985cf6d4869028f5370b636016011059be
4
- data.tar.gz: b47174e2024be883a1a0b25a63c6678acb7af154
3
+ metadata.gz: 4554661f81cd76709a2fdd32fd9eefb348941870
4
+ data.tar.gz: dbc66eb9a981b1991c391d5d2ff3800abdf3467d
5
5
  SHA512:
6
- metadata.gz: 80ccc546b9f990086bbabcf9213d0ec2d0a47afed41f7092b4a8572e44a889658a7cc31f2a6d1cdcca9e6b27e6119f1cc63053b2547b3b8e6e5dcacf684054e3
7
- data.tar.gz: 849ea28b7d3fb78006cddbff7e4f6f53aeb1da8f5d551dc1f2c925d7f03c4f7598fc930011132c1473897b79170a62a68a7506221641313531668f09ca23d9b8
6
+ metadata.gz: fc570d3d1f4f828822721b3cb478408137e5ac6f804903e7da44a0190d29b48bb4d9ce4945f6cfc4c796c6bca7cb7088124241d652389d013ca2c32449af23f1
7
+ data.tar.gz: bef4b20e3361d1c6eb176e0f9898042fb159b0577713eddebbe9d755c16dc2c36e6f41624f9a11e044dd6f77116cfe0eea0c0a56d1a30c182fb7e6d5b66796f1
data/README.md CHANGED
@@ -1,99 +1,59 @@
1
- # I18nMultitenant
1
+ # i18n Multi-Tenant
2
2
 
3
- This gem extend the standard i18n backend (I18n::Backend::Simple) to introduce {Conventions over configuration}[http://en.wikipedia.org/wiki/Convention_over_configuration] about 2 new types of locale files.
4
-
5
- WARNING: Have to rethink parts of the implementation which seem to reimplement I18n::Backend::Fallbacks without this nice to have convention over configuration "algorithm".
6
-
7
- So you can also write this with a little help of i18n's basic fallback module (at bootstrap process):
3
+ This gem is a small utility that provides the basic configuration to perform
4
+ tenant-specific translations in multi-tenant apps.
8
5
 
6
+ ## Setting the locale
9
7
  ```ruby
10
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
11
-
12
- I18n.fallbacks[:es] = [:es]
13
- I18n.fallbacks[:tenant_name_es] = [:tenant_name_es, :es]
8
+ I18nMultitenant.set(locale: :en, tenant: 'Tenant Name')
14
9
  ```
15
10
 
16
- This gem will just save you these lines through a convenient convention over configuration "algorithm".
17
-
18
- ## Conventions
11
+ ## Locale files
19
12
 
20
- ### *_formal.yml
13
+ The library leverages the use of fallbacks, using the tenant name as a locale variant.
21
14
 
22
- Given you want to offer your users the option to be addressed formally or informally through a {session locale switch}[https://github.com/Applicat/i18n_multitenant#backend-locale-switch]:
15
+ You can organize the files in nested folders as you find suitable, the only
16
+ requirement is that the root of a translation file uses the following convention:
23
17
 
24
- Then this is a {DRY}[http://en.wikipedia.org/wiki/Don%27t_repeat_yourself] solution for the workaround about having duplication of de locales in the de_formal namespace even though informal & formal translation are the same.
25
-
26
- This locale file will own all translations from its base *.yml and lets you override them through the same translation keys (except of the deviant locale namespaces de and de_formal at the beginning).
27
-
28
- ### #{locales_path}/tenants/your_tenant_name/**/your_tenant_name.yml
18
+ ```yaml
19
+ lang-TENANT_NAME:
20
+ ...
21
+ ```
29
22
 
30
- Given you want to have tenant specific locales through a {session locale switch}[https://github.com/Applicat/i18n_multitenant#backend-locale-switch]:
23
+ For a few different examples, check out the [locale files used in tests](https://github.com/ElMassimo/i18n_multitenant/tree/master/spec/support/config), but here are few valid roots:
31
24
 
32
- <b>Precondition:</b> Assure that you recursively add locale files to i18n's locale path e.g. through your Rails 3 application.rb:
25
+ ```yaml
26
+ en:
27
+ en-US:
28
+ en-US-TENANT_NAME:
29
+ ```
33
30
 
34
- config.i18n.load_path +# Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
31
+ Any name can be passed through the `:tenant` option, and it will be normalized
32
+ to be uppercase and not contain any hyphens, dots, or spaces.
35
33
 
36
- You may like to enable multi tenancy for your rails application by deploying the repository branch on multiple servers each with a different tenant-specific application constants file to load by a config system like {rails_config}[https://github.com/railsjedi/rails_config] at initialization.
34
+ If you need to use names that are not be valid yml keys even after this process,
35
+ you will need to sanitize the names yourself before handing them over to `set`.
37
36
 
38
- This locale file owns all translations from its base *.yml and optional *_formal.yml under #{locales_path}/tenants/your-tenant-name/ (recurively) and lets you override them through the same translation keys (except of the deviant locale namespaces de and de_formal at the beginning).
39
37
 
40
38
  ## Installation
41
39
 
42
- Add this to your Gemfile and run the +bundle+ command.
43
-
44
- gem "i18n_multitenant"
45
-
46
- The gem (# engine) will automatically set the backend for your Rails application.
47
-
48
- OPTIONAL: you have to manually set the i18n backend for your non-rails Ruby application:
49
-
50
- I18n.backend # I18nMultitenant::Backend.new
51
-
52
- ## Backend Locale Switch
53
-
54
- Available since version 0.0.3.
40
+ Add this line to your application's Gemfile and run `bundle install`:
55
41
 
56
- <b>Initializer</b>
57
-
58
- To initialize the global default locale based on current configuration.
59
-
60
- Put this file for instance under config/initializer/multi_tenancy.rb
61
-
62
- I18n.locale # I18n.backend.available_locale(
63
- formal: Settings.address_formally, tenant: Settings.tenant.name
64
- )
65
-
66
- Settings is a constant for your settings brought by most popular rack app config system: https://github.com/railsjedi/rails_config
67
-
68
- I18n.locale # I18n.backend.available_locale(formal: Settings.formal, tenant: Settings.tenant.name)
69
-
70
- <b>Controller</b>
71
-
72
- Dependency: https://github.com/iain/http_accept_language
73
-
74
- class ApplicationController
75
- AVAILABLE_LOCALES # %w{de en}
42
+ ```ruby
43
+ gem 'i18n_multitenant'
44
+ ```
76
45
 
77
- before_filter :set_locale
46
+ Or install it yourself running:
78
47
 
79
- private
48
+ ```sh
49
+ gem install i18n_multitenant
50
+ ```
80
51
 
81
- def set_locale
82
- if user_signed_in?
83
- # use of devise helper methods: user_signed_in?, current_user
84
- I18n.locale # current_user.language # you have to add a language string column to your schema
85
- else
86
- locale # request.preferred_language_from AVAILABLE_LOCALES
87
- locale ||# request.compatible_language_from AVAILABLE_LOCALES
88
- locale ||# I18n.default_locale
52
+ ## Configuration
89
53
 
90
- unless Settings.tenant.name ## 'global'
91
- locale # ("#{Settings.tenant.name.downcase.strip.tr(' ', '_')}_#{locale}").to_sym
92
- end
93
- end
54
+ In Rails everything should be configured out of the box, but you can perform
55
+ the configuration yourself in other applications by calling:
94
56
 
95
- I18n.locale # I18n.backend.available_locale(
96
- base_locale: locale, formal: Settings.formal_address, tenant: Settings.tenant.name
97
- )
98
- end
99
- end
57
+ ```ruby
58
+ I18nMultitenant.configure(I18n) # or pass I18n.config
59
+ ```
@@ -1,6 +1,5 @@
1
1
  class I18nMultitenant::Railtie < ::Rails::Railtie
2
2
  config.before_initialize do |app|
3
- I18n.config.enforce_available_locales = false
4
- I18n.backend = I18nMultitenant::Backend.new
3
+ I18nMultitenant.configure(I18n)
5
4
  end
6
5
  end
@@ -1,3 +1,3 @@
1
1
  module I18nMultitenant
2
- VERSION = '0.0.0'
2
+ VERSION = '0.0.1'
3
3
  end
@@ -1,3 +1,39 @@
1
- require 'i18n_multitenant/version'
2
- require 'i18n_multitenant/backend'
1
+ require 'i18n'
2
+
3
+ # frozen_string_literal: true
4
+ module I18nMultitenant
5
+
6
+ # Public: Sets the internal locale to consider the current tenant and base locale.
7
+ #
8
+ # locale: The base locale to be set (optional: uses the default locale).
9
+ # tenant: Name of the current tenant (optional: does not scope to the tenant).
10
+ #
11
+ # Returns the key that should be used for all translations specific to that
12
+ # locale and tenant configuration.
13
+ #
14
+ # Example:
15
+ # I18nMultitenant.set(locale: :en, tenant: 'Veridian Dynamics')
16
+ # => "en-VERIDIAN_DYNAMICS"
17
+ #
18
+ # I18nMultitenant.set(locale: 'en-GB', tenant: :'strange.tenant-name')
19
+ # => "en-GB-STRANGE_TENANT_NAME"
20
+ #
21
+ # I18nMultitenant.set(locale: :es)
22
+ # => :es
23
+ def self.set(locale: I18n.default_locale, tenant: nil)
24
+ unless tenant.nil? || tenant.empty?
25
+ locale = "#{ locale }-#{ tenant.to_s.upcase.tr(' .-', '_') }"
26
+ end
27
+
28
+ I18n.locale = locale
29
+ end
30
+
31
+ # Public: Configures an instance of I18n::Config to ensure fallbacks are setup.
32
+ def self.configure(config, enforce_available_locales: false)
33
+ config.enforce_available_locales = enforce_available_locales
34
+ config.backend.class.send(:include, I18n::Backend::Fallbacks)
35
+ config.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
36
+ end
37
+ end
38
+
3
39
  require 'i18n_multitenant/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ def t(*args)
5
+ I18n.translate(*args)
6
+ end
7
+
8
+ RSpec.describe I18nMultitenant do
9
+ Given {
10
+ I18nMultitenant.configure(I18n, enforce_available_locales: true)
11
+ }
12
+
13
+ describe 'set' do
14
+ context 'setting only the locale' do
15
+ When(:result) { I18nMultitenant.set(locale: locale) }
16
+
17
+ context 'regular locale' do
18
+ Given(:locale) { :es }
19
+ Then { result == :es }
20
+ And { I18n.locale == result }
21
+ end
22
+
23
+ context 'specific locale' do
24
+ Given(:locale) { 'en-GB' }
25
+ Then { result == 'en-GB' }
26
+ And { I18n.locale == result.to_sym }
27
+ end
28
+
29
+ context 'non existing locale' do
30
+ Given(:locale) { 'en-US' }
31
+ Then { expect(result).to have_failed(I18n::InvalidLocale, /not a valid locale/) }
32
+ end
33
+ end
34
+
35
+ context 'setting only the tenant' do
36
+ When(:result) { I18nMultitenant.set(tenant: tenant) }
37
+ Invariant { I18n.locale == result.to_sym }
38
+
39
+ context 'regular tenant' do
40
+ Given(:tenant) { 'Free' }
41
+ Then { result == 'en-FREE' }
42
+ end
43
+
44
+ context 'weird casing tenant' do
45
+ Given(:tenant) { 'eNtErPrIsE' }
46
+ Then { result == 'en-ENTERPRISE' }
47
+ end
48
+
49
+ context 'non existing tenant' do
50
+ Given { I18n.enforce_available_locales = false }
51
+ Given(:tenant) { :'Strange.teNant-namE' }
52
+ Then { result == 'en-STRANGE_TENANT_NAME' }
53
+ end
54
+ end
55
+
56
+ context 'setting both' do
57
+ When(:result) { I18nMultitenant.set(locale: locale, tenant: tenant) }
58
+
59
+ context 'regular tenant and locale' do
60
+ Given(:locale) { :es }
61
+ Given(:tenant) { 'eNtErPrIsE' }
62
+ Then { result == 'es-ENTERPRISE' }
63
+ And { I18n.locale == result.to_sym }
64
+ end
65
+
66
+ context 'regular tenant and specific locale' do
67
+ Given(:locale) { 'en-US' }
68
+ Given(:tenant) { :free }
69
+ Then { result == 'en-US-FREE' }
70
+ And { I18n.locale == result.to_sym }
71
+ end
72
+
73
+ context 'non existing tenant/locale combination' do
74
+ Given(:locale) { 'en-US' }
75
+ Given(:tenant) { 'Enterprise' }
76
+ Then { expect(result).to have_failed(I18n::InvalidLocale, /"en-US-ENTERPRISE" is not a valid locale/) }
77
+ end
78
+ end
79
+ end
80
+
81
+ describe 'translation' do
82
+ context 'setting the locale directly' do
83
+ context 'english' do
84
+ When { I18n.locale = :en }
85
+ Then { t(:app_name) == 'I18n' }
86
+ And { t(:greeting) == 'Welcome!' }
87
+ And { t('simple_form.labels.user.name') == 'Name' }
88
+ And { t('devise.failure.already_authenticated') == 'You are already signed in.' }
89
+ end
90
+
91
+ context 'spanish' do
92
+ When { I18n.locale = :es }
93
+ Then { t('app_name') == 'I18n' }
94
+ And { t('greeting') == 'Bienvenido!' }
95
+ And { t('simple_form.labels.user.name') == 'Nombre' }
96
+ And { t('devise.failure.already_authenticated') == 'Ya has iniciado sesión.' }
97
+ end
98
+
99
+ context 'non-existing locale' do
100
+ When(:locale) { I18n.locale = 'en-US' }
101
+ Then { expect(locale).to have_failed(I18n::InvalidLocale, /not a valid locale/) }
102
+ end
103
+
104
+ context 'locale and tenant (internal representation)' do
105
+ When { I18n.locale = 'en-FREE' }
106
+ Then { t(:greeting) == 'Welcome!' }
107
+ end
108
+ end
109
+
110
+ context 'setting the locale and tenant' do
111
+ Given {
112
+ I18nMultitenant.set(tenant: tenant, locale: locale)
113
+ }
114
+
115
+ context 'english' do
116
+ Given(:locale) { :en }
117
+
118
+ context 'Free Tenant' do
119
+ Given(:tenant) { 'Free' }
120
+ Then { t(:app_name) == 'I18n (community edition)' }
121
+ And { t(:greeting) == 'Welcome!' }
122
+ And { t('simple_form.labels.user.name') == 'Name' }
123
+ And { t('devise.failure.already_authenticated') == 'You are already signed in.' }
124
+ end
125
+
126
+ context 'Enterprise Tenant' do
127
+ Given(:tenant) { 'eNterPrise' }
128
+ Then { t(:app_name) == 'I18n Enterprise' }
129
+ And { t(:greeting) == 'Welcome!' }
130
+ And { t('simple_form.labels.user.name') == 'Name' }
131
+ And { t('devise.failure.already_authenticated') == 'You are already signed in.' }
132
+ end
133
+ end
134
+
135
+ context 'spanish' do
136
+ Given(:locale) { :es }
137
+
138
+ context 'Free Tenant' do
139
+ Given(:tenant) { :free }
140
+ Then { t('app_name') == 'I18n (versión gratuita)' }
141
+ And { t('greeting') == 'Bienvenido!' }
142
+ And { t('simple_form.labels.user.name') == 'Nombre' }
143
+ And { t('devise.failure.already_authenticated') == 'Ya iniciaste sesión.' }
144
+ end
145
+
146
+ context 'Enterprise Tenant' do
147
+ Given(:tenant) { :enterprise }
148
+ Then { t('app_name') == 'I18n PRO' }
149
+ And { t('greeting') == 'Bienvenido!' }
150
+ And { t('simple_form.labels.user.name') == 'Nombre y Apellido' }
151
+ And { t('devise.failure.already_authenticated') == 'Ya has iniciado sesión.' }
152
+ end
153
+ end
154
+
155
+ context 'specific locale with tenant' do
156
+ Given(:locale) { 'en-US' }
157
+ Given(:tenant) { 'Free' }
158
+ Then { t(:app_name) == 'I18n (community edition)' }
159
+ end
160
+
161
+ context 'nil tenant' do
162
+ Given(:locale) { :en }
163
+ Given(:tenant) { nil }
164
+ Then { t(:greeting) == 'Welcome!' }
165
+ end
166
+
167
+ context 'empty tenant' do
168
+ Given(:locale) { :es }
169
+ Given(:tenant) { '' }
170
+ Then { t(:greeting) == 'Bienvenido!' }
171
+ end
172
+ end
173
+ end
174
+ end
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,7 @@ end
6
6
 
7
7
  require 'rspec/given'
8
8
  require 'pry'
9
+ require 'pathname'
9
10
  require 'i18n_multitenant'
10
11
 
11
12
  RSpec.configure do |config|
@@ -17,3 +18,9 @@ end
17
18
  Dir['./spec/support/**/*.rb'].each do |f|
18
19
  require f
19
20
  end
21
+
22
+ module Rails
23
+ def self.root
24
+ Pathname.new('spec/support')
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ devise:
3
+ failure:
4
+ already_authenticated: 'You are already signed in.'
@@ -0,0 +1,4 @@
1
+ es:
2
+ devise:
3
+ failure:
4
+ already_authenticated: 'Ya has iniciado sesión.'
@@ -0,0 +1,2 @@
1
+ en-GB:
2
+ greeting: 'Welcome! How do you do?'
@@ -0,0 +1,3 @@
1
+ en:
2
+ app_name: 'I18n'
3
+ greeting: 'Welcome!'
@@ -0,0 +1,2 @@
1
+ es:
2
+ greeting: 'Bienvenido!'
@@ -0,0 +1,5 @@
1
+ en:
2
+ simple_form:
3
+ labels:
4
+ user:
5
+ name: 'Name'
@@ -0,0 +1,5 @@
1
+ es:
2
+ simple_form:
3
+ labels:
4
+ user:
5
+ name: 'Nombre'
@@ -0,0 +1,2 @@
1
+ en-ENTERPRISE:
2
+ app_name: "I18n Enterprise"
@@ -0,0 +1,2 @@
1
+ es-ENTERPRISE:
2
+ app_name: "I18n PRO"
@@ -0,0 +1,5 @@
1
+ es-ENTERPRISE:
2
+ simple_form:
3
+ labels:
4
+ user:
5
+ name: 'Nombre y Apellido'
@@ -0,0 +1,4 @@
1
+ es-FREE:
2
+ devise:
3
+ failure:
4
+ already_authenticated: 'Ya iniciaste sesión.'
@@ -0,0 +1,2 @@
1
+ en-FREE:
2
+ app_name: 'I18n (community edition)'
@@ -0,0 +1,2 @@
1
+ en-US-FREE:
2
+ app_name: 'I18n (community edition)'
@@ -0,0 +1,2 @@
1
+ es-FREE:
2
+ app_name: "I18n (versión gratuita)"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n_multitenant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Máximo Mussini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-11 00:00:00.000000000 Z
11
+ date: 2017-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.7'
27
27
  description: I18nMultitenant allows you to specify translations that are tenant-specific,
28
28
  falling back to the base locale.
29
29
  email:
@@ -37,22 +37,24 @@ files:
37
37
  - README.md
38
38
  - Rakefile
39
39
  - lib/i18n_multitenant.rb
40
- - lib/i18n_multitenant/backend.rb
41
40
  - lib/i18n_multitenant/railtie.rb
42
41
  - lib/i18n_multitenant/version.rb
43
- - spec/fixtures/de.yml
44
- - spec/fixtures/de_formal.yml
45
- - spec/fixtures/devise.de.yml
46
- - spec/fixtures/devise.de_formal.yml
47
- - spec/fixtures/en.yml
48
- - spec/fixtures/tenants/another_tenant_name/another_tenant_name_fr_formal.yml
49
- - spec/fixtures/tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml
50
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml
51
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml
52
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_en.yml
53
- - spec/lib/i18n_multitenant/backend_spec.rb
42
+ - spec/lib/i18n_multitenant_spec.rb
54
43
  - spec/spec_helper.rb
55
- - spec/support/shared_contexts/all_locale_file_constellations.rb
44
+ - spec/support/config/locales/devise.en.yml
45
+ - spec/support/config/locales/devise.es.yml
46
+ - spec/support/config/locales/en-GB.yml
47
+ - spec/support/config/locales/en.yml
48
+ - spec/support/config/locales/es.yml
49
+ - spec/support/config/locales/simple_form.en.yml
50
+ - spec/support/config/locales/simple_form.es.yml
51
+ - spec/support/config/locales/tenants/enterprise/en-ENTERPRISE.yml
52
+ - spec/support/config/locales/tenants/enterprise/es-ENTERPRISE.yml
53
+ - spec/support/config/locales/tenants/enterprise/simple_form.es-ENTERPRISE.yml
54
+ - spec/support/config/locales/tenants/free/devise.es-FREE.yml
55
+ - spec/support/config/locales/tenants/free/en-FREE.yml
56
+ - spec/support/config/locales/tenants/free/en-US-FREE.yml
57
+ - spec/support/config/locales/tenants/free/es-FREE.yml
56
58
  homepage: https://github.com/ElMassimo/i18n_multitenant
57
59
  licenses:
58
60
  - MIT
@@ -66,7 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
68
  requirements:
67
69
  - - ">="
68
70
  - !ruby/object:Gem::Version
69
- version: 2.0.0
71
+ version: '2.0'
70
72
  required_rubygems_version: !ruby/object:Gem::Requirement
71
73
  requirements:
72
74
  - - ">="
@@ -79,16 +81,19 @@ signing_key:
79
81
  specification_version: 4
80
82
  summary: Translation for multi-tenant applications.
81
83
  test_files:
82
- - spec/fixtures/de.yml
83
- - spec/fixtures/de_formal.yml
84
- - spec/fixtures/devise.de.yml
85
- - spec/fixtures/devise.de_formal.yml
86
- - spec/fixtures/en.yml
87
- - spec/fixtures/tenants/another_tenant_name/another_tenant_name_fr_formal.yml
88
- - spec/fixtures/tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml
89
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml
90
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml
91
- - spec/fixtures/tenants/your_tenant_name/your_tenant_name_en.yml
92
- - spec/lib/i18n_multitenant/backend_spec.rb
84
+ - spec/lib/i18n_multitenant_spec.rb
93
85
  - spec/spec_helper.rb
94
- - spec/support/shared_contexts/all_locale_file_constellations.rb
86
+ - spec/support/config/locales/devise.en.yml
87
+ - spec/support/config/locales/devise.es.yml
88
+ - spec/support/config/locales/en-GB.yml
89
+ - spec/support/config/locales/en.yml
90
+ - spec/support/config/locales/es.yml
91
+ - spec/support/config/locales/simple_form.en.yml
92
+ - spec/support/config/locales/simple_form.es.yml
93
+ - spec/support/config/locales/tenants/enterprise/en-ENTERPRISE.yml
94
+ - spec/support/config/locales/tenants/enterprise/es-ENTERPRISE.yml
95
+ - spec/support/config/locales/tenants/enterprise/simple_form.es-ENTERPRISE.yml
96
+ - spec/support/config/locales/tenants/free/devise.es-FREE.yml
97
+ - spec/support/config/locales/tenants/free/en-FREE.yml
98
+ - spec/support/config/locales/tenants/free/en-US-FREE.yml
99
+ - spec/support/config/locales/tenants/free/es-FREE.yml
@@ -1,134 +0,0 @@
1
- require 'i18n'
2
-
3
- class I18nMultitenant::Backend < I18n::Backend::Simple
4
- FORMAL_FILENAME_PATTERN = /_formal\.[a-zA-Z]+$/
5
- FORMAL_LOCALE_PATTERN = /_formal$/
6
-
7
- TENANT_FILENAME_PATTERN = /tenants/
8
- TENANT_LOCALE_PATTERN = /tenants$/
9
-
10
- attr_accessor :filenames
11
-
12
- # Accepts a list of paths to translation files. Loads translations from
13
- # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
14
- # for details.
15
- def load_translations(*filenames)
16
- filenames = I18n.load_path if filenames.empty?
17
-
18
- @filenames = filenames.flatten
19
-
20
- @filenames.each { |filename| load_file(filename) }
21
- end
22
-
23
- def available_locale(options = {})
24
- options.assert_valid_keys(:base_locale, :formal, :tenant) if options.respond_to? :assert_valid_keys
25
-
26
- base_locale = options[:base_locale] || I18n.default_locale
27
- formal = options[:formal] || false
28
- tenant = (options[:tenant] || '').downcase.strip.tr(' ', '_')
29
-
30
- deepest_available_locale = nil
31
-
32
- # take last / deepest available locale of possible combinations
33
- variant_index = -1
34
-
35
- [
36
- [
37
- (formal && tenant),
38
- [tenant, base_locale, 'formal']
39
- ],
40
- [
41
- formal && tenant && available_locales.include?([tenant, base_locale].join('_').to_sym) &&
42
- available_locales.include?([base_locale, 'formal'].join('_').to_sym),
43
- [tenant, base_locale, 'formal']
44
- ],
45
- [tenant, [tenant, base_locale]],
46
- [formal, [base_locale, 'formal']],
47
- [true, [base_locale]],
48
- ].each do |variant|
49
- variant_index += 1
50
-
51
- next unless variant[0]
52
-
53
- if available_locales.include?(variant[1].join('_').to_sym) || (variant_index == 1 && [tenant, base_locale, 'formal'] == variant[1])
54
- deepest_available_locale = variant[1].join('_').to_sym
55
- end
56
-
57
- break if deepest_available_locale
58
- end
59
-
60
- deepest_available_locale || I18n.default_locale
61
- end
62
-
63
- protected
64
-
65
- # Looks up a translation from the translations hash. Returns nil if
66
- # eiher key is nil, or locale, scope or key do not exist as a key in the
67
- # nested translations hash. Splits keys or scopes containing dots
68
- # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
69
- # <tt>%w(currency format)</tt>.
70
- def lookup(locale, key, scope = [], options = {})
71
- init_translations unless initialized?
72
-
73
- # only formal address: [:de, :de_formal]
74
- # only multi tenancy: [:de, :tenant_name_de]
75
- # formal address & multi tenancy: [:de, :tenant_name_de, :de_formal, :tenant_name_de_formal]
76
-
77
- locales = []
78
-
79
- base_locale = locale.to_s.gsub(FORMAL_LOCALE_PATTERN, '')
80
-
81
- tenant = tenant_from_locale?(locale)
82
- base_locale.gsub!(/^#{tenant}_/, '') if tenant
83
-
84
- locales << base_locale.to_sym
85
-
86
- if locale.to_s.match(FORMAL_LOCALE_PATTERN) && tenant && locale.to_s.match(/^#{tenant}_/)
87
- locales << locale.to_s.gsub(FORMAL_LOCALE_PATTERN, '').to_sym
88
- locales << locale.to_s.gsub(/^#{tenant}_/, '').to_sym
89
- end
90
-
91
- locales << locale unless locales.include?(locale)
92
-
93
- entry, last_entry = nil, nil
94
-
95
- locales.each do |locale|
96
- keys = I18n.normalize_keys(locale, key, scope, options[:separator])
97
-
98
- entry = keys.inject(translations) do |result, _key|
99
- _key = _key.to_sym
100
-
101
- unless result.is_a?(Hash) && result.has_key?(_key)
102
- nil
103
- else
104
- result = result[_key]
105
- result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
106
- result
107
- end
108
- end
109
-
110
- if entry.nil?
111
- entry = last_entry
112
- else
113
- last_entry = entry
114
- end
115
- end
116
-
117
- entry
118
- end
119
-
120
- private
121
-
122
- def tenant_from_locale?(locale)
123
- tenant = locale.to_s.gsub(FORMAL_LOCALE_PATTERN, '').split('_')
124
-
125
- if tenant.length > 2
126
- tenant.pop # pop languages like de
127
- tenant = tenant.join('_')
128
- else
129
- tenant = nil
130
- end
131
-
132
- tenant && @filenames.select{|f| f.match("/tenants/#{tenant}/")}.any? ? tenant : nil
133
- end
134
- end
data/spec/fixtures/de.yml DELETED
@@ -1,4 +0,0 @@
1
- de:
2
- formal_available: 'Du'
3
- formal_unavailable: 'Du'
4
- de_formal_formal_available: 'Du'
@@ -1,3 +0,0 @@
1
- de_formal:
2
- formal_available: 'Sie'
3
- de_formal_formal_available: 'Sie'
@@ -1,4 +0,0 @@
1
- de:
2
- devise:
3
- example: 'Dein Beispiel'
4
- informal_example: 'Dein anderes Beispiel'
@@ -1,4 +0,0 @@
1
- de_formal:
2
- devise:
3
- example: Ihr Beispiel
4
- examples: Ihre Beispiele
data/spec/fixtures/en.yml DELETED
@@ -1,2 +0,0 @@
1
- en:
2
- example: 'Dummy'
@@ -1,2 +0,0 @@
1
- another_tenant_name_fr_formal:
2
- formal_available: 'jamais vous'
@@ -1,3 +0,0 @@
1
- your_tenant_name_de_formal:
2
- devise:
3
- example: Ihr formelles Beispiel
@@ -1,3 +0,0 @@
1
- your_tenant_name_de:
2
- formal_available: 'Du auch'
3
- formal_unavailable_again: 'Du auch wieder'
@@ -1,2 +0,0 @@
1
- your_tenant_name_de_formal:
2
- formal_available: 'Sie auch'
@@ -1,2 +0,0 @@
1
- your_tenant_name_en:
2
- example: 'Dummy change'
@@ -1,204 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe I18nMultitenant::Backend do
4
- before :all do
5
- I18n.config.enforce_available_locales = false
6
- I18n.default_locale = :de
7
- I18n.locale = I18n.default_locale
8
- end
9
-
10
- describe '#lookup' do
11
- include_context :all_locale_file_constellations
12
-
13
- before :all do
14
- I18n.backend = I18nMultitenant::Backend.new
15
- end
16
-
17
- context 'default' do
18
- context 'formal translation available' do
19
- context 'returns the translation' do
20
- Given { I18n.locale = :de }
21
- Then { I18n.t('formal_available') == 'Du' }
22
- Then { I18n.t('de_formal_formal_available') == 'Du' }
23
- end
24
-
25
- context 'returns the formal translation' do
26
- Given { I18n.locale = :de_formal }
27
- Then { I18n.t('formal_available') == 'Sie' }
28
- Then { I18n.t('de_formal_formal_available') == 'Sie' }
29
- end
30
- end
31
-
32
- context 'formal translation unavailable' do
33
- context 'inerits the informal locales from :de' do
34
- Given { I18n.locale = :de_formal }
35
- Then { I18n.t('formal_unavailable') == 'Du' }
36
- end
37
- end
38
- end
39
-
40
- context 'tenant-specific' do
41
- context 'formal translation available' do
42
- context 'returns the translation' do
43
- Given { I18n.locale = :your_tenant_name_de }
44
- Then { I18n.t('formal_available') == 'Du auch' }
45
- Then { I18n.t('de_formal_formal_available') == 'Du' }
46
- end
47
-
48
- context 'returns the formal translation' do
49
- Given { I18n.locale = :your_tenant_name_de_formal }
50
- Then { I18n.t('formal_available') == 'Sie auch' }
51
- Then { I18n.t('de_formal_formal_available') == 'Sie' }
52
- end
53
- end
54
-
55
- context 'formal translation unavailable' do
56
- context 'inerits the informal locales from :de and :your_tenant_name_de' do
57
- Given { I18n.locale = :your_tenant_name_de_formal }
58
- Then { I18n.t('formal_unavailable') == 'Du' }
59
- Then { I18n.t('formal_unavailable_again') == 'Du auch wieder' }
60
- end
61
- end
62
-
63
- context 'formal translation available for locale without base locale' do
64
- context 'inits the locale without inheritation of unavailable base locale' do
65
- Given { I18n.locale = :another_tenant_name_fr_formal }
66
- Then { I18n.t('formal_available') == 'jamais vous' }
67
- end
68
- end
69
- end
70
-
71
- context 'locale files with more than 1 dot' do
72
- context 'principally works' do
73
- Given { I18n.locale = :de }
74
- Then { I18n.t('devise.example') == 'Dein Beispiel' }
75
- end
76
-
77
- context 'principally works' do
78
- Given { I18n.locale = :de_formal }
79
- Then { I18n.t('devise.example') == 'Ihr Beispiel' }
80
- end
81
-
82
- context 'principally works tenant' do
83
- Given { I18n.locale = :your_tenant_name_de_formal }
84
- Then { I18n.t('devise.informal_example') == 'Dein anderes Beispiel' } # devise.de.yml
85
- Then { I18n.t('devise.examples') == 'Ihre Beispiele' } # devise.de_formal.yml
86
- Then { I18n.t('devise.example') == 'Ihr formelles Beispiel' } # tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml
87
- end
88
- end
89
- end
90
-
91
- describe '.available_locale' do
92
- context 'any locale' do
93
- before :all do
94
- I18n.load_path = [
95
- File.expand_path('../../../fixtures/de.yml', __FILE__),
96
- File.expand_path('../../../fixtures/en.yml', __FILE__)
97
- ]
98
- end
99
-
100
- # .available_locale any locale principally works
101
- it 'principally works' do
102
- I18n.backend = I18nMultitenant::Backend.new
103
-
104
- [
105
- [{}, :de],
106
- [{base_locale: :en}, :en],
107
- [{base_locale: :unavailable}, :de],
108
- [{tenant: 'Your Tenant Name'}, :de],
109
- [{formal: true, tenant: 'Your Tenant Name'}, :de],
110
- [{formal: true, tenant: 'Unavailable Tenant Name'}, :de]
111
- ].each do |variant|
112
- actual = I18n.backend.available_locale(variant.first)
113
- expect(actual).to(
114
- be(variant.last),
115
- "input #{variant.first.inspect} should result in the output: #{variant.last.inspect} but got #{actual.inspect}"
116
- )
117
- end
118
- end
119
- end
120
-
121
- context 'any formal locale' do
122
- before :all do
123
- I18n.load_path = [
124
- File.expand_path('../../../fixtures/de.yml', __FILE__),
125
- File.expand_path('../../../fixtures/de_formal.yml', __FILE__)
126
- ]
127
- end
128
-
129
- it 'principally works' do
130
- I18n.backend = I18nMultitenant::Backend.new
131
-
132
- [
133
- [{formal: true}, :de_formal],
134
- [{formal: true, tenant: 'Your Tenant Name'}, :de_formal]
135
- ].each do |variant|
136
- actual = I18n.backend.available_locale(variant.first)
137
- expect(actual).to(
138
- be(variant.last),
139
- "input #{variant.first.inspect} should result in the output: #{variant.last.inspect} but got #{actual.inspect}"
140
- )
141
- end
142
- end
143
- end
144
-
145
- context 'any formal locale plus tenant specific locale' do
146
- before :all do
147
- I18n.load_path = [
148
- File.expand_path('../../../fixtures/de.yml', __FILE__),
149
- File.expand_path('../../../fixtures/de_formal.yml', __FILE__),
150
- File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de.yml', __FILE__),
151
- File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_en.yml', __FILE__)
152
- ]
153
- end
154
-
155
- it 'principally works' do
156
- I18n.backend = I18nMultitenant::Backend.new
157
-
158
- [
159
- [{formal: true}, :de_formal],
160
- # should be :your_tenant_name_de_formal even though there is no :your_tenant_name_de_formal file
161
- # but the base file :de_formal to inherit from
162
- [{formal: true, tenant: 'Your Tenant Name'}, :your_tenant_name_de_formal],
163
- [{base_locale: :en, formal: true, tenant: 'Your Tenant Name'}, :your_tenant_name_en]
164
- ].each do |variant|
165
- actual = I18n.backend.available_locale(variant.first)
166
- expect(actual).to(
167
- be(variant.last),
168
- "input #{variant.first.inspect} should result in the output: #{variant.last.inspect} but got #{actual.inspect}"
169
- )
170
- end
171
- end
172
- end
173
-
174
- context 'any formal tenant-specific locale' do
175
- include_context :all_locale_file_constellations
176
-
177
- it 'principally works' do
178
- I18n.backend = I18nMultitenant::Backend.new
179
- end
180
- end
181
-
182
- context 'tenant has special locale without an equivalent base locale' do
183
- before :all do
184
- I18n.load_path = [
185
- File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml', __FILE__)
186
- ]
187
- end
188
-
189
- it 'principally works' do
190
- I18n.backend = I18nMultitenant::Backend.new
191
-
192
- [
193
- [{base_locale: :de, formal: true, tenant: 'Your Tenant Name'}, :your_tenant_name_de_formal]
194
- ].each do |variant|
195
- actual = I18n.backend.available_locale(variant.first)
196
- expect(actual).to(
197
- be(variant.last),
198
- "input #{variant.first.inspect} should result in the output: #{variant.last.inspect} but got #{actual.inspect}"
199
- )
200
- end
201
- end
202
- end
203
- end
204
- end
@@ -1,14 +0,0 @@
1
- RSpec.shared_context :all_locale_file_constellations do
2
- before :all do
3
- I18n.load_path = [
4
- File.expand_path('../../../fixtures/de.yml', __FILE__),
5
- File.expand_path('../../../fixtures/de_formal.yml', __FILE__),
6
- File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de.yml', __FILE__),
7
- File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml', __FILE__),
8
- File.expand_path('../../../fixtures/tenants/another_tenant_name/another_tenant_name_fr_formal.yml', __FILE__),
9
- File.expand_path('../../../fixtures/devise.de.yml', __FILE__),
10
- File.expand_path('../../../fixtures/devise.de_formal.yml', __FILE__),
11
- File.expand_path('../../../fixtures/tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml', __FILE__),
12
- ]
13
- end
14
- end