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.
- checksums.yaml +4 -4
- data/README.md +37 -77
- data/lib/i18n_multitenant/railtie.rb +1 -2
- data/lib/i18n_multitenant/version.rb +1 -1
- data/lib/i18n_multitenant.rb +38 -2
- data/spec/lib/i18n_multitenant_spec.rb +174 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/config/locales/devise.en.yml +4 -0
- data/spec/support/config/locales/devise.es.yml +4 -0
- data/spec/support/config/locales/en-GB.yml +2 -0
- data/spec/support/config/locales/en.yml +3 -0
- data/spec/support/config/locales/es.yml +2 -0
- data/spec/support/config/locales/simple_form.en.yml +5 -0
- data/spec/support/config/locales/simple_form.es.yml +5 -0
- data/spec/support/config/locales/tenants/enterprise/en-ENTERPRISE.yml +2 -0
- data/spec/support/config/locales/tenants/enterprise/es-ENTERPRISE.yml +2 -0
- data/spec/support/config/locales/tenants/enterprise/simple_form.es-ENTERPRISE.yml +5 -0
- data/spec/support/config/locales/tenants/free/devise.es-FREE.yml +4 -0
- data/spec/support/config/locales/tenants/free/en-FREE.yml +2 -0
- data/spec/support/config/locales/tenants/free/en-US-FREE.yml +2 -0
- data/spec/support/config/locales/tenants/free/es-FREE.yml +2 -0
- metadata +35 -30
- data/lib/i18n_multitenant/backend.rb +0 -134
- data/spec/fixtures/de.yml +0 -4
- data/spec/fixtures/de_formal.yml +0 -3
- data/spec/fixtures/devise.de.yml +0 -4
- data/spec/fixtures/devise.de_formal.yml +0 -4
- data/spec/fixtures/en.yml +0 -2
- data/spec/fixtures/tenants/another_tenant_name/another_tenant_name_fr_formal.yml +0 -2
- data/spec/fixtures/tenants/your_tenant_name/devise.your_tenant_name_de_formal.yml +0 -3
- data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml +0 -3
- data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml +0 -2
- data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_en.yml +0 -2
- data/spec/lib/i18n_multitenant/backend_spec.rb +0 -204
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4554661f81cd76709a2fdd32fd9eefb348941870
|
4
|
+
data.tar.gz: dbc66eb9a981b1991c391d5d2ff3800abdf3467d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc570d3d1f4f828822721b3cb478408137e5ac6f804903e7da44a0190d29b48bb4d9ce4945f6cfc4c796c6bca7cb7088124241d652389d013ca2c32449af23f1
|
7
|
+
data.tar.gz: bef4b20e3361d1c6eb176e0f9898042fb159b0577713eddebbe9d755c16dc2c36e6f41624f9a11e044dd6f77116cfe0eea0c0a56d1a30c182fb7e6d5b66796f1
|
data/README.md
CHANGED
@@ -1,99 +1,59 @@
|
|
1
|
-
#
|
1
|
+
# i18n Multi-Tenant
|
2
2
|
|
3
|
-
This gem
|
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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
## Conventions
|
11
|
+
## Locale files
|
19
12
|
|
20
|
-
|
13
|
+
The library leverages the use of fallbacks, using the tenant name as a locale variant.
|
21
14
|
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
### #{locales_path}/tenants/your_tenant_name/**/your_tenant_name.yml
|
18
|
+
```yaml
|
19
|
+
lang-TENANT_NAME:
|
20
|
+
...
|
21
|
+
```
|
29
22
|
|
30
|
-
|
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
|
-
|
25
|
+
```yaml
|
26
|
+
en:
|
27
|
+
en-US:
|
28
|
+
en-US-TENANT_NAME:
|
29
|
+
```
|
33
30
|
|
34
|
-
|
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
|
-
|
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
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
46
|
+
Or install it yourself running:
|
78
47
|
|
79
|
-
|
48
|
+
```sh
|
49
|
+
gem install i18n_multitenant
|
50
|
+
```
|
80
51
|
|
81
|
-
|
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
|
-
|
91
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
end
|
57
|
+
```ruby
|
58
|
+
I18nMultitenant.configure(I18n) # or pass I18n.config
|
59
|
+
```
|
data/lib/i18n_multitenant.rb
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
-
require '
|
2
|
-
|
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
|
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.
|
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
|
+
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/
|
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/
|
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
|
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/
|
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/
|
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
data/spec/fixtures/de_formal.yml
DELETED
data/spec/fixtures/devise.de.yml
DELETED
data/spec/fixtures/en.yml
DELETED
@@ -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
|