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.
@@ -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.
@@ -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.
@@ -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,8 @@
1
+ require 'i18n'
2
+
3
+ require 'multi_formal_i18n_tenancy/backend'
4
+
5
+ require 'multi_formal_i18n_tenancy/engine' if defined? Rails::Engine
6
+
7
+ module MultiFormalI18nTenancy
8
+ end
@@ -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,7 @@
1
+ module RailsInfo
2
+ class Engine < ::Rails::Engine
3
+ config.before_initialize do |app|
4
+ I18n.backend = MultiFormalI18nTenancy::Backend.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module MultiFormalI18nTenancy
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ de:
2
+ formal_available: 'Du'
3
+ formal_unavailable: 'Du'
@@ -0,0 +1,2 @@
1
+ de_formal:
2
+ formal_available: 'Sie'
@@ -0,0 +1,3 @@
1
+ your_tenant_name_de:
2
+ formal_available: 'Du auch'
3
+ formal_unavailable_again: 'Du auch wieder'
@@ -0,0 +1,2 @@
1
+ your_tenant_name_de_formal:
2
+ formal_available: 'Sie auch'
@@ -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
@@ -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