i18n_multitenant 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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