multi_formal_i18n_tenancy 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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +63 -0
- data/Rakefile +31 -0
- data/lib/multi_formal_i18n_tenancy.rb +8 -0
- data/lib/multi_formal_i18n_tenancy/backend.rb +91 -0
- data/lib/multi_formal_i18n_tenancy/engine.rb +7 -0
- data/lib/multi_formal_i18n_tenancy/version.rb +3 -0
- data/spec/fixtures/de.yml +3 -0
- data/spec/fixtures/de_formal.yml +2 -0
- data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml +3 -0
- data/spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml +2 -0
- data/spec/lib/multi_formal_i18n_tenancy/backend_spec.rb +57 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/deferred_garbage_collection.rb +43 -0
- metadata +96 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= MultiFormalI18nTenancy {<img src="https://secure.travis-ci.org/Applicat/multi_formal_i18n_tenancy.png" />}[http://travis-ci.org/Applicat/multi_formal_i18n_tenancy] {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/Applicat/multi_formal_i18n_tenancy] {<img src="https://gemnasium.com/Applicat/multi_formal_i18n_tenancy.png?travis"/>}[https://gemnasium.com/Applicat/multi_formal_i18n_tenancy]
|
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
|
+
<b>*_formal.yml</b>
|
6
|
+
|
7
|
+
Given you want to offer your users the option to be addressed formally or informally through a session locale switch:
|
8
|
+
|
9
|
+
I18n.locale = :de # or :de_formal
|
10
|
+
|
11
|
+
So 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.
|
12
|
+
|
13
|
+
This locale file owns 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).
|
14
|
+
|
15
|
+
<b>#{locales_path}/tenants/your_tenant_name/**/your_tenant_name.yml</b>
|
16
|
+
|
17
|
+
Given you want to have tenant specific locales through a session locale switch:
|
18
|
+
|
19
|
+
I18n.locale :your_tenant_name_de # or :your_tenant_name_de_formal
|
20
|
+
|
21
|
+
('Your Tenant Name'.parameterize.gsub('-', '_') + '_de').to_sym == :your_tenant_name_de
|
22
|
+
|
23
|
+
<strong>Precondition:</strong> Assure that you recursively add locale files to i18n's locale path e.g. through your Rails 3 application.rb:
|
24
|
+
|
25
|
+
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
|
26
|
+
|
27
|
+
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).
|
28
|
+
|
29
|
+
== Installation
|
30
|
+
|
31
|
+
Add this to your Gemfile and run the +bundle+ command.
|
32
|
+
|
33
|
+
gem "multi_formal_i18n_tenancy"
|
34
|
+
|
35
|
+
The gem (= engine) will automatically set the backend for your Rails application.
|
36
|
+
|
37
|
+
OPTIONAL: you have to manually set the i18n backend for your non-rails Ruby application:
|
38
|
+
|
39
|
+
I18n.backend = MultiFormalI18nTenancy::Backend.new
|
40
|
+
|
41
|
+
== Wiki Docs
|
42
|
+
|
43
|
+
* {Modules}[https://github.com/Applicat/multi_formal_i18n_tenancy/wiki/modules]
|
44
|
+
|
45
|
+
== Compatibility
|
46
|
+
|
47
|
+
Tested on MacOS with: Rails 3.1 & Ruby 1.9.2, Rails 3.2.6 & Ruby 1.9.3.
|
48
|
+
|
49
|
+
= Future =
|
50
|
+
|
51
|
+
* Support of more backends than only standard simple one
|
52
|
+
|
53
|
+
== Contribution
|
54
|
+
|
55
|
+
Just follow the screencast of Ryan Bates on railscasts.com:
|
56
|
+
|
57
|
+
http://railscasts.com/episodes/300-contributing-to-open-source
|
58
|
+
|
59
|
+
Add a description about your changes to CHANGELOG.md under section multi_formal_i18n_tenancy (unreleased).
|
60
|
+
|
61
|
+
== License
|
62
|
+
|
63
|
+
This project uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
require "rspec/core/rake_task"
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new("spec")
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rdoc/task'
|
14
|
+
rescue LoadError
|
15
|
+
require 'rdoc/rdoc'
|
16
|
+
require 'rake/rdoctask'
|
17
|
+
RDoc::Task = Rake::RDocTask
|
18
|
+
end
|
19
|
+
|
20
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
22
|
+
rdoc.title = 'MultiFormalI18nTenancy'
|
23
|
+
rdoc.options << '--line-numbers'
|
24
|
+
rdoc.rdoc_files.include('README.rdoc')
|
25
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
|
+
end
|
27
|
+
|
28
|
+
Bundler::GemHelper.install_tasks
|
29
|
+
|
30
|
+
task :default => :spec
|
31
|
+
task :test => :spec
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class MultiFormalI18nTenancy::Backend < I18n::Backend::Simple
|
2
|
+
FORMAL_FILENAME_PATTERN = /_formal\.[a-zA-Z]+$/
|
3
|
+
FORMAL_LOCALE_PATTERN = /_formal$/
|
4
|
+
|
5
|
+
TENANT_FILENAME_PATTERN = /tenants/
|
6
|
+
TENANT_LOCALE_PATTERN = /tenants$/
|
7
|
+
|
8
|
+
attr_accessor :filenames
|
9
|
+
|
10
|
+
# Accepts a list of paths to translation files. Loads translations from
|
11
|
+
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
|
12
|
+
# for details.
|
13
|
+
def load_translations(*filenames)
|
14
|
+
filenames = I18n.load_path if filenames.empty?
|
15
|
+
|
16
|
+
@filenames = filenames.flatten
|
17
|
+
|
18
|
+
[
|
19
|
+
# locale file groups order (ancestor chain)
|
20
|
+
#
|
21
|
+
# a) de > de_formal > your_enterprise_name_de > your_enterprise_name_de_formal
|
22
|
+
# b) de > your_enterprise_name_de
|
23
|
+
|
24
|
+
# de
|
25
|
+
->(f) { !f.match(FORMAL_FILENAME_PATTERN) && !f.match(TENANT_FILENAME_PATTERN) },
|
26
|
+
# de > your_enterprise_name_de
|
27
|
+
->(f) { !f.match(FORMAL_FILENAME_PATTERN) && f.match(TENANT_FILENAME_PATTERN) },
|
28
|
+
# de > de_formal
|
29
|
+
->(f) { f.match(FORMAL_FILENAME_PATTERN) && !f.match(TENANT_FILENAME_PATTERN) },
|
30
|
+
# de > de_formal > your_enterprise_name_de > your_enterprise_name_de_formal
|
31
|
+
->(f) { f.match(FORMAL_FILENAME_PATTERN) && f.match(TENANT_FILENAME_PATTERN) }
|
32
|
+
].each do |filename_filter|
|
33
|
+
filenames.flatten.select{|f| filename_filter.call(f) }.each { |filename| load_file(filename) }
|
34
|
+
end
|
35
|
+
|
36
|
+
@filenames = [] # free memory
|
37
|
+
end
|
38
|
+
|
39
|
+
# Stores translations for the given locale in memory.
|
40
|
+
# This uses a deep merge for the translations hash, so existing
|
41
|
+
# translations will be overwritten by new ones only at the deepest
|
42
|
+
# level of the hash.
|
43
|
+
def store_translations(locale, data, options = {})
|
44
|
+
locale = locale.to_sym
|
45
|
+
|
46
|
+
# the has_key check assures that the inheritance will be performed for the first locale file
|
47
|
+
if (locale.to_s.match(FORMAL_LOCALE_PATTERN) || tenant_from_locale?(locale)) && !translations.has_key?(locale)
|
48
|
+
# inherit keys from base locale file
|
49
|
+
base_locale = locale.to_s
|
50
|
+
tenant = tenant_from_locale?(locale)
|
51
|
+
|
52
|
+
if tenant && locale.to_s.match(FORMAL_LOCALE_PATTERN)
|
53
|
+
# de > de_formal
|
54
|
+
base_locale = locale.to_s.gsub(/^#{tenant}_/, '')
|
55
|
+
translations[locale] = (translations[base_locale.to_sym] || {}).clone
|
56
|
+
|
57
|
+
# de_formal > your_enterprise_name_de
|
58
|
+
base_locale = locale.to_s.gsub(FORMAL_LOCALE_PATTERN, '')
|
59
|
+
base_translations = (translations[base_locale.to_sym] || {}).clone.deep_symbolize_keys # deep_symbolize_keys?
|
60
|
+
translations[locale].deep_merge!(base_translations)
|
61
|
+
elsif tenant
|
62
|
+
base_locale.gsub!(/^#{tenant}_/, '')
|
63
|
+
else
|
64
|
+
base_locale.gsub!(FORMAL_LOCALE_PATTERN, '')
|
65
|
+
end
|
66
|
+
|
67
|
+
translations[locale] = (translations[base_locale.to_sym] || {}).clone
|
68
|
+
else
|
69
|
+
translations[locale] ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
data = data.deep_symbolize_keys
|
73
|
+
|
74
|
+
translations[locale].deep_merge!(data)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def tenant_from_locale?(locale)
|
80
|
+
tenant = locale.to_s.gsub(FORMAL_LOCALE_PATTERN, '').split('_')
|
81
|
+
|
82
|
+
if tenant.length > 2
|
83
|
+
tenant.pop # pop languages like de
|
84
|
+
tenant = tenant.join('_')
|
85
|
+
else
|
86
|
+
tenant = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
tenant && @filenames.select{|f| f.match("/tenants/#{tenant}/")}.any? ? tenant : nil
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# ruby gems
|
4
|
+
require 'i18n'
|
5
|
+
|
6
|
+
require 'multi_formal_i18n_tenancy'
|
7
|
+
|
8
|
+
describe MultiFormalI18nTenancy::Backend do
|
9
|
+
before :all do
|
10
|
+
I18n.backend = MultiFormalI18nTenancy::Backend.new
|
11
|
+
I18n.load_path = [
|
12
|
+
File.expand_path('../../../fixtures/de.yml', __FILE__),
|
13
|
+
File.expand_path('../../../fixtures/de_formal.yml', __FILE__),
|
14
|
+
File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de.yml', __FILE__),
|
15
|
+
File.expand_path('../../../fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml', __FILE__)
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.translate' do
|
20
|
+
context 'default' do
|
21
|
+
context 'formal translation available' do
|
22
|
+
it 'returns the formal translation' do
|
23
|
+
I18n.locale = :de
|
24
|
+
I18n.t('formal_available').should == 'Du'
|
25
|
+
I18n.locale = :de_formal
|
26
|
+
I18n.t('formal_available').should == 'Sie'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'formal translation unavailable' do
|
31
|
+
it 'inerits the informal locales from :de' do
|
32
|
+
I18n.locale = :de_formal
|
33
|
+
I18n.t('formal_unavailable').should == 'Du'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'tenant-specific' do
|
39
|
+
context 'formal translation available' do
|
40
|
+
it 'returns the formal translation' do
|
41
|
+
I18n.locale = :your_tenant_name_de
|
42
|
+
I18n.t('formal_available').should == 'Du auch'
|
43
|
+
I18n.locale = :your_tenant_name_de_formal
|
44
|
+
I18n.t('formal_available').should == 'Sie auch'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'formal translation unavailable' do
|
49
|
+
it 'inerits the informal locales from :de and :your_tenant_name_de' do
|
50
|
+
I18n.locale = :your_tenant_name_de_formal
|
51
|
+
I18n.t('formal_unavailable').should == 'Du'
|
52
|
+
I18n.t('formal_unavailable_again').should == 'Du auch wieder'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
unless defined? Rails
|
2
|
+
class Rails
|
3
|
+
def self.root
|
4
|
+
File.expand_path('dummy', __FILE__)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.env
|
8
|
+
'test'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ENV["RAILS_ENV"] ||= 'test'
|
14
|
+
|
15
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f }
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.mock_with :rspec
|
19
|
+
end
|
20
|
+
|
21
|
+
# https://makandracards.com/makandra/950-speed-up-rspec-by-deferring-garbage-collection
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.before(:all) do
|
24
|
+
DeferredGarbageCollection.start
|
25
|
+
end
|
26
|
+
config.after(:all) do
|
27
|
+
DeferredGarbageCollection.reconsider
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# https://makandracards.com/makandra/950-speed-up-rspec-by-deferring-garbage-collection
|
2
|
+
class DeferredGarbageCollection
|
3
|
+
DEFERRED_GC_THRESHOLD = (ENV['DEFER_GC'] || 10.0).to_f #used to be 10.0
|
4
|
+
|
5
|
+
@@last_gc_run = Time.now
|
6
|
+
|
7
|
+
def self.start
|
8
|
+
return if unsupported_enviroment
|
9
|
+
GC.disable if DEFERRED_GC_THRESHOLD > 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.memory_threshold
|
13
|
+
@mem = %x(free 2>/dev/null).to_s.split(" ")
|
14
|
+
return nil if @mem.empty?
|
15
|
+
@mem[15].to_i / (@mem[7].to_i/100)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reconsider
|
19
|
+
return if unsupported_enviroment
|
20
|
+
|
21
|
+
if (percent_used = self.memory_threshold)
|
22
|
+
running_out_of_memory = percent_used > 90
|
23
|
+
|
24
|
+
# just for info, as soon as we got some numbers remove it
|
25
|
+
swap_percent_used = @mem[19].to_i / (@mem[18].to_i/100) rescue 0
|
26
|
+
puts "percent memory used #{percent_used} (#{@mem[8]} of #{@mem[7]})"
|
27
|
+
puts "percent swap used #{swap_percent_used} (#{@mem[19]} of #{@mem[18]})"
|
28
|
+
else
|
29
|
+
running_out_of_memory = false
|
30
|
+
end
|
31
|
+
|
32
|
+
if( (DEFERRED_GC_THRESHOLD > 0 && Time.now - @@last_gc_run >= DEFERRED_GC_THRESHOLD) || running_out_of_memory )
|
33
|
+
GC.enable
|
34
|
+
GC.start
|
35
|
+
GC.disable
|
36
|
+
@@last_gc_run = Time.now
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.unsupported_enviroment
|
41
|
+
ENV['TRAVIS'] # TODO: enable for ruby 1.9.3 or more RAM
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multi_formal_i18n_tenancy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mathias Gawlista
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: i18n
|
16
|
+
requirement: &70279376278580 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70279376278580
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70279376293860 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.11.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70279376293860
|
36
|
+
description: Your formal locales will inherit translations from their base locale
|
37
|
+
and locales stored in an enterprise folder can override base + formal translations
|
38
|
+
email:
|
39
|
+
- gawlista@googlemail.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- lib/multi_formal_i18n_tenancy/backend.rb
|
45
|
+
- lib/multi_formal_i18n_tenancy/engine.rb
|
46
|
+
- lib/multi_formal_i18n_tenancy/version.rb
|
47
|
+
- lib/multi_formal_i18n_tenancy.rb
|
48
|
+
- MIT-LICENSE
|
49
|
+
- Rakefile
|
50
|
+
- README.rdoc
|
51
|
+
- spec/fixtures/de.yml
|
52
|
+
- spec/fixtures/de_formal.yml
|
53
|
+
- spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml
|
54
|
+
- spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml
|
55
|
+
- spec/lib/multi_formal_i18n_tenancy/backend_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
- spec/support/deferred_garbage_collection.rb
|
58
|
+
homepage: http://applicat.github.com/multi_formal_i18n_tenancy
|
59
|
+
licenses: []
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
hash: 2196548884169363665
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: 2196548884169363665
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.17
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Your formal locales will inherit translations from their base locale and
|
88
|
+
locales stored in an enterprise folder can override base + formal translations
|
89
|
+
test_files:
|
90
|
+
- spec/fixtures/de.yml
|
91
|
+
- spec/fixtures/de_formal.yml
|
92
|
+
- spec/fixtures/tenants/your_tenant_name/your_tenant_name_de.yml
|
93
|
+
- spec/fixtures/tenants/your_tenant_name/your_tenant_name_de_formal.yml
|
94
|
+
- spec/lib/multi_formal_i18n_tenancy/backend_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/support/deferred_garbage_collection.rb
|