multi_formal_i18n_tenancy 0.0.1

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