translation 0.1 → 0.3

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -0
  3. data/lib/translation.rb +75 -0
  4. data/lib/translation/client.rb +27 -0
  5. data/lib/translation/client/base_operation.rb +49 -0
  6. data/lib/translation/client/base_operation/create_new_mo_files_step.rb +22 -0
  7. data/lib/translation/client/base_operation/dump_haml_gettext_keys_step.rb +44 -0
  8. data/lib/translation/client/base_operation/dump_slim_gettext_keys_step.rb +43 -0
  9. data/lib/translation/client/base_operation/save_new_po_files_step.rb +31 -0
  10. data/lib/translation/client/base_operation/save_new_yaml_files_step.rb +37 -0
  11. data/lib/translation/client/base_operation/save_special_yaml_files_step.rb +53 -0
  12. data/lib/translation/client/init_operation.rb +42 -0
  13. data/lib/translation/client/init_operation/cleanup_yaml_files_step.rb +52 -0
  14. data/lib/translation/client/init_operation/create_yaml_po_files_step.rb +53 -0
  15. data/lib/translation/client/init_operation/update_and_collect_po_files_step.rb +33 -0
  16. data/lib/translation/client/init_operation/update_pot_file_step.rb +18 -0
  17. data/lib/translation/client/purge_operation.rb +13 -0
  18. data/lib/translation/client/sync_operation.rb +41 -0
  19. data/lib/translation/client/sync_operation/create_yaml_pot_file_step.rb +46 -0
  20. data/lib/translation/client/sync_operation/update_and_collect_pot_file_step.rb +18 -0
  21. data/lib/translation/config.rb +20 -0
  22. data/lib/translation/controller.rb +18 -0
  23. data/lib/translation/flat_hash.rb +112 -0
  24. data/lib/translation/railtie.rb +37 -0
  25. data/lib/translation/tasks.rb +31 -0
  26. data/lib/translation/yaml_conversion.rb +81 -0
  27. metadata +104 -11
  28. data/README.rdoc +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d107d21e5d34e6c3196473d241e89ce9dfdc6068
4
- data.tar.gz: 24850c42bf206998b93f066cd1a7ed182df94521
3
+ metadata.gz: 8d9ba5b1f146ab9c345be6542b3c2b02a7645855
4
+ data.tar.gz: ecb4386329547f2b0894146b69374c650f2dcd10
5
5
  SHA512:
6
- metadata.gz: b2474e725b88ccba46604c91b470710ad28aae86ce2219dd57620ccae1baa984268f4d59fc02f527485610b8595eecd996d93b0ed794a4359fb902dcd45932f1
7
- data.tar.gz: 7e08ab1177dbb8a667caea4ca4334f14bcb2ce5215519228ad30a6c6c3b7feb3403f3926ecb98095db10180a92cafbfe570b6a09bd914af6b20c97d93b041cc7
6
+ metadata.gz: 4f1af0d393bee32f48cd04b5a453436d7e353e83ca0c26fcbb7133997301a9f296b31305c3a2d76600b9ce909c87b3076a5ab25aa8c88ad5706122a69a9f8240
7
+ data.tar.gz: 952f4b1cbf1bdbe7083d8997aaad7f41b25bd9865bbb1cb76d02e96ec8d14dcef74206803224f1cc7b1efd2cfee55c24d56cd8553c4b8dbf9cda1259ff1996b8
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # gem for [Rails Translation](http://rails.translation.io)
2
+
3
+ ## Description
4
+
5
+ Add this gem to your [Rails](http://rubyonrails.org) app to translate it with [Rails Translation](http://rails.translation.io).
6
+
7
+ ## Installation
8
+
9
+ Add the gem to your project's Gemfile :
10
+
11
+ gem 'translation'
12
+
13
+ Then :
14
+
15
+ * Create a translation project from the UI.
16
+ * Copy the initializer into your Rails app (`config/initializers/translation.rb`)
17
+
18
+ And finish by inititalizing your translation project with :
19
+
20
+ bundle exec rake translation:init
21
+
22
+ ## Synchronization
23
+
24
+ To get new translations from Rails Translation and to send new translatable strings, simply run :
25
+
26
+ bundle exec rake translation:sync
27
+
28
+ ## Purge
29
+
30
+ If you need to remove unused keys taking the current branch as reference :
31
+
32
+ bundle exec rake translation:purge
33
+
34
+ Note that this operation will also perform a sync at the same time.
35
+
36
+ Warning : all keys that are not present in the current branch will be **permanently deleted both on Rails Translation and in your app**.
37
+
38
+ ## Tests
39
+
40
+ To run the specs :
41
+
42
+ bundle
43
+ bundle exec rspec
44
+
45
+ ## Credits
46
+
47
+ The [translation gem](https://rubygems.org/gems/translation) in released under MIT license by [Aurélien Malisart](http://aurelien.malisart.be) and [Michaël Hoste](http://80limit.com) (see MIT-LICENSE
48
+ file).
49
+
50
+ (c) http://rails.translation.io
data/lib/translation.rb CHANGED
@@ -1,2 +1,77 @@
1
+ require 'net/http'
2
+
3
+ require 'gettext'
4
+ require 'gettext/po'
5
+ require 'gettext/po_parser'
6
+ require 'gettext/tools'
7
+ require 'gettext/text_domain_manager'
8
+
1
9
  module Translation
10
+ GETTEXT_METHODS = [
11
+ :_, :n_, :p_, :s_, :np_, :ns_, :N_, :Nn,
12
+ :gettext, :sgettext, :ngettext, :nsgettext, :pgettext, :npgettext
13
+ ]
14
+
15
+ TEXT_DOMAIN = 'app'
16
+ SOURCE_FILES_PATTERN = '**/*.{rb,erb,html.erb,xml.erb}'
17
+ end
18
+
19
+ require 'translation/config'
20
+ require 'translation/railtie'
21
+ require 'translation/client'
22
+ require 'translation/flat_hash'
23
+ require 'translation/yaml_conversion'
24
+
25
+ require 'translation/controller'
26
+
27
+ module Translation
28
+ module Proxy
29
+ include GetText
30
+ end
31
+
32
+ class << self
33
+ attr_reader :config, :client
34
+
35
+ def configure(&block)
36
+ if Rails.env.development?
37
+ GetText::TextDomainManager.cached = false
38
+ end
39
+
40
+ @config ||= Config.new
41
+
42
+ yield @config
43
+
44
+ Proxy.bindtextdomain(TEXT_DOMAIN, {
45
+ :path => @config.locales_path,
46
+ :charset => 'utf-8'
47
+ })
48
+
49
+ Proxy.textdomain(TEXT_DOMAIN)
50
+ Object.delegate *GETTEXT_METHODS, :to => Proxy
51
+
52
+ @client = Client.new(@config.api_key, @config.endpoint)
53
+
54
+ return true
55
+ end
56
+
57
+ def pot_path
58
+ File.join(Translation.config.locales_path, "#{TEXT_DOMAIN}.pot")
59
+ end
60
+
61
+ def info(message, level = 0, verbose_level = 0)
62
+ verbose = @config.try(:verbose) || 0
63
+ if verbose >= verbose_level
64
+ indent = (1..level).to_a.collect { " " }.join('')
65
+ puts "#{indent}* #{message}"
66
+ end
67
+ end
68
+
69
+ def normalize_path(relative_or_absolute_path)
70
+ File.expand_path(relative_or_absolute_path).gsub("#{Dir.pwd}/", '')
71
+ end
72
+
73
+ def version
74
+ Gem::Specification::find_by_name('translation').version.to_s
75
+ end
76
+ end
2
77
  end
@@ -0,0 +1,27 @@
1
+ require 'translation/client/base_operation'
2
+ require 'translation/client/init_operation'
3
+ require 'translation/client/sync_operation'
4
+ require 'translation/client/purge_operation'
5
+
6
+ module Translation
7
+ class Client
8
+ attr_reader :api_key, :endpoint
9
+
10
+ def initialize(api_key, endpoint)
11
+ @api_key = api_key
12
+ @endpoint = endpoint
13
+ end
14
+
15
+ def init
16
+ Translation::Client::InitOperation.new(self).run
17
+ end
18
+
19
+ def sync
20
+ Translation::Client::SyncOperation.new(self).run
21
+ end
22
+
23
+ def purge
24
+ Translation::Client::SyncOperation.new(self).run(true)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'translation/client/base_operation/save_new_po_files_step'
2
+ require 'translation/client/base_operation/create_new_mo_files_step'
3
+ require 'translation/client/base_operation/save_new_yaml_files_step'
4
+ require 'translation/client/base_operation/save_special_yaml_files_step'
5
+ require 'translation/client/base_operation/dump_haml_gettext_keys_step'
6
+ require 'translation/client/base_operation/dump_slim_gettext_keys_step'
7
+
8
+ module Translation
9
+ class Client
10
+ class BaseOperation
11
+ attr_accessor :client, :params
12
+
13
+ GETTEXT_ENTRY_RE = Regexp.new('(?:' + Translation::GETTEXT_METHODS.join('|') + ')\([^)]+\)?\)')
14
+
15
+ def initialize(client, perform_real_requests = true)
16
+ @client = client
17
+ @params = {
18
+ 'gem_version' => Translation.version,
19
+ 'target_languages[]' => Translation.config.target_locales.map(&:to_s)
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def perform_request(uri, params)
26
+ begin
27
+ http = Net::HTTP.new(uri.host, uri.port)
28
+ http.read_timeout = 500
29
+
30
+ request = Net::HTTP::Post.new(uri.request_uri)
31
+ request.set_form_data(params)
32
+
33
+ response = http.request(request)
34
+ parsed_response = JSON.parse(response.body)
35
+
36
+ if response.code.to_i == 200
37
+ return parsed_response
38
+ elsif response.code.to_i == 400 && parsed_response.has_key?('error')
39
+ $stderr.puts "[Error] #{parsed_response['error']}"
40
+ else
41
+ $stderr.puts "[Error] Unknown error."
42
+ end
43
+ rescue Errno::ECONNREFUSED
44
+ $stderr.puts "[Error] Server not responding."
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class CreateNewMoFilesStep
5
+ def initialize(locales_path)
6
+ @locales_path = locales_path
7
+ end
8
+
9
+ def run
10
+ Translation.info "Creating new MO files."
11
+
12
+ Dir["#{@locales_path}/*/#{TEXT_DOMAIN}.po"].each do |po_path|
13
+ mo_path = "#{File.dirname(po_path)}/LC_MESSAGES/app.mo"
14
+ Translation.info mo_path, 2, 2
15
+ FileUtils.mkdir_p(File.dirname(mo_path))
16
+ GetText::Tools::MsgFmt.run(po_path, '-o', mo_path)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class DumpHamlGettextKeysStep
5
+ def initialize(haml_source_files)
6
+ @haml_source_files = haml_source_files
7
+ end
8
+
9
+ def run
10
+ if @haml_source_files.any? && defined?(Haml)
11
+ Translation.info "Extracting Gettext entries from HAML files."
12
+
13
+ File.open(File.join('tmp', 'translation-haml-gettext.rb'), 'w') do |file|
14
+ extracted_gettext_entries.each do |entry|
15
+ file.puts "#{entry}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def extracted_gettext_entries
24
+ entries = []
25
+
26
+ @haml_source_files.each do |haml_file_path|
27
+ Translation.info haml_file_path, 2, 2
28
+
29
+ haml_data = File.read(haml_file_path)
30
+ ruby_data = Haml::Engine.new(haml_data).precompiled
31
+
32
+ ruby_data.scan(GETTEXT_ENTRY_RE).each do |entry|
33
+ entries << entry
34
+ end
35
+ end
36
+
37
+ Translation.info "#{entries.size} entries found", 2, 2
38
+
39
+ entries
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class DumpSlimGettextKeysStep
5
+ def initialize(slim_source_files)
6
+ @slim_source_files = slim_source_files
7
+ end
8
+
9
+ def run
10
+ if @slim_source_files.any? && defined?(Slim)
11
+ Translation.info "Extracting Gettext entries from SLIM files."
12
+
13
+ File.open(File.join('tmp', 'translation-slim-gettext.rb'), 'w') do |file|
14
+ extracted_gettext_entries.each do |entry|
15
+ file.puts "#{entry}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def extracted_gettext_entries
24
+ entries = []
25
+
26
+ @slim_source_files.each do |slim_file_path|
27
+ Translation.info slim_file_path, 2, 2
28
+
29
+ ruby_data = Slim::Template.new(slim_file_path, {}).precompiled_template
30
+
31
+ ruby_data.scan(GETTEXT_ENTRY_RE).each do |entry|
32
+ entries << entry
33
+ end
34
+ end
35
+
36
+ Translation.info "#{entries.size} entries found", 2, 2
37
+
38
+ entries
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class SaveNewPoFilesStep
5
+ def initialize(target_locales, locales_path, parsed_response)
6
+ @target_locales = target_locales
7
+ @locales_path = locales_path
8
+ @parsed_response = parsed_response
9
+ end
10
+
11
+ def run
12
+ Translation.info "Saving new PO files."
13
+
14
+ @target_locales.each do |target_locale|
15
+ if @parsed_response.has_key?("po_data_#{target_locale}")
16
+ po_path = File.join(@locales_path, target_locale.to_s.gsub('-', '_'), "#{TEXT_DOMAIN}.po")
17
+ FileUtils.mkdir_p(File.dirname(po_path))
18
+ Translation.info po_path, 2, 2
19
+
20
+ File.open(po_path, 'wb') do |file|
21
+ file.write(@parsed_response["po_data_#{target_locale}"])
22
+ end
23
+ end
24
+ end
25
+
26
+ return self
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class SaveNewYamlFilesStep
5
+ def initialize(target_locales, yaml_locales_path, parsed_response)
6
+ @target_locales = target_locales
7
+ @yaml_locales_path = yaml_locales_path
8
+ @parsed_response = parsed_response
9
+ end
10
+
11
+ def run
12
+ Translation.info "Saving new translation YAML files."
13
+
14
+ @target_locales.each do |target_locale|
15
+ if @parsed_response.has_key?("yaml_po_data_#{target_locale}")
16
+ FileUtils.mkdir_p(@yaml_locales_path)
17
+ yaml_path = File.join(@yaml_locales_path, "translation.#{target_locale}.yml")
18
+ Translation.info yaml_path, 2, 2
19
+ yaml_data = YAMLConversion.get_yaml_data_from_po_data(@parsed_response["yaml_po_data_#{target_locale}"], target_locale)
20
+
21
+ top_comment = <<EOS
22
+ # WARNING. THIS FILE WAS AUTO-GENERATED BY THE TRANSLATION GEM.
23
+ # IF YOU UPDATE IT, YOUR CHANGES WILL BE LOST AT THE NEXT SYNC.
24
+ EOS
25
+
26
+ File.open(yaml_path, 'wb') do |file|
27
+ file.write("---\n")
28
+ file.write(top_comment)
29
+ file.write(yaml_data.split("---\n", 2).last)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ module Translation
2
+ class Client
3
+ class BaseOperation
4
+ class SaveSpecialYamlFilesStep
5
+
6
+ def initialize(source_locale, target_locales, yaml_locales_path, yaml_file_paths)
7
+ @source_locale = source_locale
8
+ @target_locales = target_locales
9
+ @yaml_locales_path = yaml_locales_path
10
+ @yaml_file_paths = yaml_file_paths
11
+ end
12
+
13
+ def run
14
+ Translation.info "Saving new localization YAML files (with non-string values)."
15
+ all_flat_translations = {}
16
+
17
+ @yaml_file_paths.each do |file_path|
18
+ all_flat_translations.merge!(
19
+ YAMLConversion.get_flat_translations_for_yaml_file(file_path)
20
+ )
21
+ end
22
+
23
+ all_flat_special_translations = all_flat_translations.select do |key, value|
24
+ !value.is_a?(String) && !value.nil?
25
+ end
26
+
27
+ source_flat_special_translations = all_flat_special_translations.select do |key|
28
+ key.start_with?("#{@source_locale}.")
29
+ end
30
+
31
+ @target_locales.each do |target_locale|
32
+ yaml_path = File.join(@yaml_locales_path, "localization.#{target_locale}.yml")
33
+
34
+ Translation.info yaml_path, 2, 2
35
+ flat_translations = {}
36
+
37
+ source_flat_special_translations.each_pair do |key, value|
38
+ target_key = key.gsub(/\A#{@source_locale}\./, "#{target_locale}.")
39
+ flat_translations[target_key] = all_flat_special_translations[target_key]
40
+ end
41
+
42
+ yaml_data = YAMLConversion.get_yaml_data_from_flat_translations(flat_translations)
43
+
44
+ File.open(yaml_path, 'wb') do |file|
45
+ file.write(yaml_data)
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ require 'translation/client/init_operation/update_pot_file_step'
2
+ require 'translation/client/init_operation/update_and_collect_po_files_step'
3
+ require 'translation/client/init_operation/create_yaml_po_files_step'
4
+ require 'translation/client/init_operation/cleanup_yaml_files_step'
5
+
6
+ module Translation
7
+ class Client
8
+ class InitOperation < BaseOperation
9
+ def run
10
+ haml_source_files = Dir['**/*.{haml}']
11
+ slim_source_files = Dir['**/*.{slim}']
12
+
13
+ BaseOperation::DumpHamlGettextKeysStep.new(haml_source_files).run
14
+ BaseOperation::DumpSlimGettextKeysStep.new(slim_source_files).run
15
+
16
+ source_files = Dir[SOURCE_FILES_PATTERN]
17
+ pot_path = Translation.pot_path
18
+ source_locale = Translation.config.source_locale
19
+ target_locales = Translation.config.target_locales
20
+ locales_path = Translation.config.locales_path
21
+ yaml_locales_path = 'config/locales'
22
+ yaml_file_paths = I18n.load_path
23
+
24
+ UpdatePotFileStep.new(pot_path, source_files).run
25
+ UpdateAndCollectPoFilesStep.new(target_locales, pot_path, locales_path).run(params)
26
+ CreateYamlPoFilesStep.new(source_locale, target_locales, yaml_file_paths).run(params)
27
+
28
+ Translation.info "Sending data to server"
29
+ uri = URI("http://#{client.endpoint}/projects/#{client.api_key}/init")
30
+ parsed_response = perform_request(uri, params)
31
+
32
+ unless parsed_response.nil?
33
+ BaseOperation::SaveNewPoFilesStep.new(target_locales, locales_path, parsed_response).run
34
+ BaseOperation::SaveNewYamlFilesStep.new(target_locales, yaml_locales_path, parsed_response).run
35
+ BaseOperation::SaveSpecialYamlFilesStep.new(source_locale, target_locales, yaml_locales_path, yaml_file_paths).run
36
+ CleanupYamlFilesStep.new(source_locale, target_locales, yaml_file_paths, yaml_locales_path).run
37
+ BaseOperation::CreateNewMoFilesStep.new(locales_path).run
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ module Translation
2
+ class Client
3
+ class InitOperation < BaseOperation
4
+ class CleanupYamlFilesStep
5
+ def initialize(source_locale, target_locales, yaml_file_paths, yaml_locales_path)
6
+ @source_locale = source_locale
7
+ @target_locales = target_locales
8
+ @yaml_file_paths = yaml_file_paths
9
+ @yaml_locales_path = yaml_locales_path
10
+ end
11
+
12
+ def run
13
+ @yaml_file_paths.each do |locale_file_path|
14
+ in_project = locale_file_path_in_project?(locale_file_path)
15
+
16
+ protected_file = @target_locales.any? do |target_locale|
17
+ paths = [
18
+ File.join(@yaml_locales_path, "translation.#{target_locale}.yml").to_s,
19
+ File.join(@yaml_locales_path, "localization.#{target_locale}.yml").to_s
20
+ ]
21
+
22
+ paths.include?(locale_file_path)
23
+ end
24
+
25
+ if in_project && !protected_file
26
+ content_hash = YAML::load(File.read(locale_file_path))
27
+ new_content_hash = content_hash.keep_if { |k| k.to_s == @source_locale.to_s }
28
+
29
+ if new_content_hash.keys.any?
30
+ Translation.info "Rewriting #{locale_file_path}", 2, 2
31
+ File.open(locale_file_path, 'wb') do |file|
32
+ file.write(new_content_hash.to_yaml)
33
+ end
34
+ else
35
+ Translation.info "Removing #{locale_file_path}", 2, 2
36
+ FileUtils.rm(locale_file_path)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def locale_file_path_in_project?(locale_file_path)
45
+ Translation.normalize_path(locale_file_path).start_with?(
46
+ Translation.normalize_path(@yaml_locales_path)
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ module Translation
2
+ class Client
3
+ class InitOperation < BaseOperation
4
+ class CreateYamlPoFilesStep
5
+ def initialize(source_locale, target_locales, yaml_file_paths)
6
+ @source_locale = source_locale
7
+ @target_locales = target_locales
8
+ @yaml_file_paths = yaml_file_paths
9
+ end
10
+
11
+ def run(params)
12
+ Translation.info "Importing translations from YAML files."
13
+ all_flat_translations = {}
14
+
15
+ @yaml_file_paths.each do |file_path|
16
+ Translation.info file_path, 2, 2
17
+ all_flat_translations.merge!(YAMLConversion.get_flat_translations_for_yaml_file(file_path))
18
+ end
19
+
20
+ all_flat_string_translations = all_flat_translations.select do |key, value|
21
+ key.present? && value.is_a?(String) && !value.nil?
22
+ end
23
+
24
+ source_flat_string_tanslations = all_flat_string_translations.select do |key|
25
+ key.start_with?("#{@source_locale}.") && !key.start_with?("#{@source_locale}.faker.")
26
+ end
27
+
28
+ @target_locales.each do |target_locale|
29
+ po_representation = GetText::PO.new
30
+
31
+ source_flat_string_tanslations.each_pair do |key, value|
32
+ target_key = key.gsub(/\A#{Translation.config.source_locale}\./, "#{target_locale}.")
33
+ msgid = value
34
+ msgstr = all_flat_string_translations[target_key]
35
+
36
+ unless msgid.blank?
37
+ po_entry = GetText::POEntry.new(:msgctxt)
38
+ po_entry.msgid = msgid
39
+ po_entry.msgstr = msgstr
40
+ po_entry.msgctxt = key.split('.', 2).last
41
+ #po_entry.references = [ value[:locale_file_path] ]
42
+
43
+ po_representation[po_entry.msgctxt, po_entry.msgid] = po_entry
44
+ end
45
+ end
46
+
47
+ params["yaml_po_data_#{target_locale}"] = po_representation.to_s
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module Translation
2
+ class Client
3
+ class InitOperation < BaseOperation
4
+ class UpdateAndCollectPoFilesStep
5
+ def initialize(target_locales, pot_path, locales_path)
6
+ @target_locales = target_locales
7
+ @pot_path = pot_path
8
+ @locales_path = locales_path
9
+ end
10
+
11
+ def run(params)
12
+ Translation.info "Updating PO files."
13
+
14
+ @target_locales.each do |target_locale|
15
+ po_path = "#{@locales_path}/#{target_locale.gsub('-', '_')}/#{TEXT_DOMAIN}.po"
16
+ Translation.info po_path, 2, 2
17
+
18
+ if File.exist?(po_path)
19
+ GetText::Tools::MsgMerge.run(po_path, @pot_path, '-o', po_path, '--no-fuzzy-matching', '--no-obsolete-entries')
20
+ else
21
+ FileUtils.mkdir_p(File.dirname(po_path))
22
+ GetText::Tools::MsgInit.run('-i', @pot_path, '-o', po_path, '-l', target_locale, '--no-translator')
23
+ end
24
+
25
+ params["po_data_#{target_locale}"] = File.read(po_path)
26
+ end
27
+
28
+ return self
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ module Translation
2
+ class Client
3
+ class InitOperation < BaseOperation
4
+ class UpdatePotFileStep
5
+ def initialize(pot_path, source_files)
6
+ @pot_path = pot_path
7
+ @source_files = source_files
8
+ end
9
+
10
+ def run
11
+ Translation.info "Updating POT file."
12
+ FileUtils.mkdir_p(File.dirname(@pot_path))
13
+ GetText::Tools::XGetText.run(*@source_files, '-o', @pot_path, '--msgid-bugs-address', 'contact@translation.io',)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # NOT USED
2
+
3
+ module Translation
4
+ class Client
5
+ class PurgeOperation < BaseOperation
6
+
7
+ def run
8
+
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require 'translation/client/sync_operation/update_and_collect_pot_file_step'
2
+ require 'translation/client/sync_operation/create_yaml_pot_file_step'
3
+
4
+ module Translation
5
+ class Client
6
+ class SyncOperation < BaseOperation
7
+ def run(purge = false)
8
+ haml_source_files = Dir['**/*.{haml}']
9
+ slim_source_files = Dir['**/*.{slim}']
10
+
11
+ BaseOperation::DumpHamlGettextKeysStep.new(haml_source_files).run
12
+ BaseOperation::DumpSlimGettextKeysStep.new(slim_source_files).run
13
+
14
+ source_files = Dir[SOURCE_FILES_PATTERN]
15
+ pot_path = Translation.pot_path
16
+ source_locale = Translation.config.source_locale
17
+ target_locales = Translation.config.target_locales
18
+ locales_path = Translation.config.locales_path
19
+ yaml_locales_path = 'config/locales'
20
+ yaml_file_paths = I18n.load_path
21
+
22
+ UpdateAndCollectPotFileStep.new(pot_path, source_files).run(params)
23
+ CreateYamlPotFileStep.new(source_locale, yaml_file_paths).run(params)
24
+
25
+ if purge
26
+ params['purge'] = 'true'
27
+ end
28
+
29
+ uri = URI("http://#{client.endpoint}/projects/#{client.api_key}/sync")
30
+ parsed_response = perform_request(uri, params)
31
+
32
+ unless parsed_response.nil?
33
+ BaseOperation::SaveNewPoFilesStep.new(target_locales, locales_path, parsed_response).run
34
+ BaseOperation::CreateNewMoFilesStep.new(locales_path).run
35
+ BaseOperation::SaveNewYamlFilesStep.new(target_locales, yaml_locales_path, parsed_response).run
36
+ BaseOperation::SaveSpecialYamlFilesStep.new(source_locale, target_locales, yaml_locales_path, yaml_file_paths).run
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ module Translation
2
+ class Client
3
+ class SyncOperation < BaseOperation
4
+ class CreateYamlPotFileStep
5
+ def initialize(source_locale, yaml_file_paths)
6
+ @source_locale = source_locale
7
+ @yaml_file_paths = yaml_file_paths
8
+ end
9
+
10
+ def run(params)
11
+ Translation.info "Generating POT file from YAML files."
12
+ all_flat_translations = {}
13
+
14
+ @yaml_file_paths.each do |file_path|
15
+ Translation.info file_path, 2, 2
16
+ all_flat_translations.merge!(
17
+ YAMLConversion.get_flat_translations_for_yaml_file(file_path)
18
+ )
19
+ end
20
+
21
+ source_flat_string_tanslations = all_flat_translations.select do |key, value|
22
+ value.is_a?(String) && key.start_with?("#{@source_locale}.") && !key.start_with?("#{@source_locale}.faker.")
23
+ end
24
+
25
+ pot_representation = GetText::PO.new
26
+
27
+ source_flat_string_tanslations.each_pair do |key, value|
28
+ msgid = value
29
+
30
+ unless msgid.blank?
31
+ pot_entry = GetText::POEntry.new(:msgctxt)
32
+ pot_entry.msgid = msgid
33
+ pot_entry.msgstr = ''
34
+ pot_entry.msgctxt = key.split('.', 2).last
35
+ #pot_entry.references = [ value[:locale_file_path] ]
36
+
37
+ pot_representation[pot_entry.msgctxt, pot_entry.msgid] = pot_entry
38
+ end
39
+ end
40
+
41
+ params['yaml_pot_data'] = pot_representation.to_s
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module Translation
2
+ class Client
3
+ class SyncOperation < BaseOperation
4
+ class UpdateAndCollectPotFileStep
5
+ def initialize(pot_path, source_files)
6
+ @pot_path = pot_path
7
+ @source_files = source_files
8
+ end
9
+
10
+ def run(params)
11
+ Translation.info "Updating POT file."
12
+ GetText::Tools::XGetText.run(*@source_files, '-o', @pot_path)
13
+ params['pot_data'] = File.read(@pot_path)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Translation
2
+ class Config
3
+ attr_accessor :api_key, :locales_path
4
+ attr_accessor :source_locale, :target_locales
5
+ attr_accessor :endpoint
6
+ attr_accessor :verbose
7
+
8
+ def initialize
9
+ self.locales_path = File.join('config', 'locales', 'gettext')
10
+ self.source_locale = :en
11
+ self.target_locales = []
12
+ self.endpoint = 'api.translation.io/api'
13
+ self.verbose = 1
14
+ end
15
+
16
+ def to_s
17
+ "#{api_key} — #{source_locale} => #{target_locales.join(' + ')}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Translation
2
+ module Controller
3
+ def set_locale
4
+ requested_locale = params[:locale] ||
5
+ session[:locale] ||
6
+ cookies[:locale] ||
7
+ request.env['HTTP_ACCEPT_LANGUAGE'] ||
8
+ I18n.default_locale
9
+
10
+ if requested_locale.to_sym.in?(I18n.available_locales)
11
+ session[:locale] = requested_locale
12
+ I18n.locale = requested_locale
13
+ else
14
+ redirect_to :root, :locale => I18n.default_locale
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,112 @@
1
+ module Translation
2
+ module FlatHash
3
+ class << self
4
+ def to_flat_hash(hash)
5
+ get_flat_hash_for_level(hash)
6
+ end
7
+
8
+ def to_hash(flat_hash)
9
+ hash = {}
10
+
11
+ flat_hash.each_pair do |key, value|
12
+ build_hash_with_flat(hash, key, value)
13
+ end
14
+
15
+ hash
16
+ end
17
+
18
+ private
19
+
20
+ def build_hash_with_flat(hash, key_string, value)
21
+ current_object = hash
22
+ splitted = key_string.split(/\.|\[/, 2)
23
+ current_key = splitted[0] # first is always a hash
24
+ key_string = splitted[1]
25
+
26
+ if key_string.blank? # if only one key like { 'en' => 'salut' }
27
+ current_object[current_key] = value
28
+ else
29
+ # Put back '[' if needed
30
+ if key_string.count(']') > key_string.count('[')
31
+ key_string = '[' + key_string
32
+ end
33
+
34
+ while key_string != ''
35
+ # Next is array
36
+ if key_string[0] == '['
37
+ array_pos = key_string.split(']', 2)[0]
38
+ array_pos = array_pos.split('[', 2)[1].to_i
39
+
40
+ key_string = key_string.split(']', 2).count == 2 ? key_string.split(']', 2)[1] : ""
41
+ key_string = key_string[1..-1] if key_string[0] == '.'
42
+
43
+ if no_key?(current_object, current_key)
44
+ current_object[current_key] = []
45
+ end
46
+
47
+ current_object = current_object[current_key]
48
+ current_key = array_pos
49
+
50
+ # array is terminal
51
+ if key_string == ''
52
+ current_object[array_pos] = value
53
+ end
54
+ # next is hash
55
+ elsif key_string[0] != '[' && (key_string.include?('.') or key_string.include?('['))
56
+ splitted = key_string.split(/\.|\[/, 2)
57
+ new_key = splitted[0]
58
+ key_string = splitted[1]
59
+
60
+ # Put back '[' if needed
61
+ if key_string.count(']') > key_string.count('[')
62
+ key_string = '[' + key_string
63
+ end
64
+
65
+ if no_key?(current_object, current_key)
66
+ current_object[current_key] = {}
67
+ end
68
+ current_object = current_object[current_key]
69
+ current_key = new_key
70
+ # next (last) is value
71
+ else
72
+ new_key = key_string
73
+
74
+ if no_key?(current_object, current_key)
75
+ current_object[current_key] = {}
76
+ end
77
+ current_object = current_object[current_key]
78
+ current_object[new_key] = value
79
+
80
+ key_string = ''
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def no_key?(array_or_hash, key)
87
+ (array_or_hash.is_a?(Hash) && !array_or_hash.has_key?(key)) || !array_or_hash[key]
88
+ end
89
+
90
+ def get_flat_hash_for_level(value, parent_key = nil)
91
+ flat_hash = {}
92
+
93
+ if value.is_a? Hash
94
+ value.each_pair do |key, value|
95
+ current_level_key = [ parent_key, key ].reject(&:blank?).join('.')
96
+ flat_hash.merge!(get_flat_hash_for_level(value, current_level_key))
97
+ end
98
+ elsif value.is_a? Array
99
+ value.each_with_index do |item, index|
100
+ flat_hash.merge!(get_flat_hash_for_level(item, "#{parent_key}[#{index}]"))
101
+ end
102
+ else
103
+ flat_hash[parent_key] = value
104
+ end
105
+
106
+ flat_hash
107
+ end
108
+
109
+ end
110
+ end
111
+ end
112
+
@@ -0,0 +1,37 @@
1
+ require 'i18n'
2
+ require 'i18n/config'
3
+
4
+ require 'gettext'
5
+ require 'gettext/po'
6
+ require 'gettext/po_parser'
7
+
8
+ module Translation
9
+ class Railtie < Rails::Railtie
10
+ rake_tasks do
11
+ require 'translation/tasks'
12
+ end
13
+
14
+ initializer 'translation.rails_extensions' do
15
+ ActionController::Base.send(:include, Translation::Controller)
16
+ end
17
+ end
18
+ end
19
+
20
+ module I18n
21
+ class Config
22
+ def locale=(locale)
23
+ I18n.enforce_available_locales!(locale)
24
+ @locale = locale.to_sym rescue nil
25
+ GetText.locale = locale.to_s.gsub('-', '_').to_sym
26
+ end
27
+ end
28
+ end
29
+
30
+ module GetText
31
+ class POParser < Racc::Parser
32
+ def initialize
33
+ @ignore_fuzzy = true
34
+ @report_warning = false
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ require 'gettext/tools'
2
+
3
+ namespace :translation do
4
+ task :config => :environment do
5
+ puts Translation.config
6
+ end
7
+
8
+ task :init => :environment do
9
+ if Translation.client
10
+ Translation.client.init
11
+ else
12
+ Translation.info "[Error] Client cannot be built. Did you set up the initializer?"
13
+ end
14
+ end
15
+
16
+ task :sync => :environment do
17
+ if Translation.client
18
+ Translation.client.sync
19
+ else
20
+ Translation.info "[Error] Client cannot be built. Did you set up the initializer?"
21
+ end
22
+ end
23
+
24
+ task :purge => :environment do
25
+ if Translation.client
26
+ Translation.client.purge
27
+ else
28
+ Translation.info "[Error] Client cannot be built. Did you set up the initializer?"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,81 @@
1
+ module Translation
2
+ module YAMLConversion
3
+ class << self
4
+
5
+ def get_pot_data_from_yaml(source_locale = nil, yaml_file_paths = nil)
6
+ source_locale = Translation.config.source_locale if source_locale.blank?
7
+ yaml_file_paths = I18n.load_path if yaml_file_paths.nil?
8
+
9
+ source_translations = YAMLConversion.get_flat_translations_for_locale(source_locale, yaml_file_paths)
10
+ pot_representation = GetText::PO.new
11
+
12
+ source_translations.each_pair do |key, value|
13
+ msgid = value
14
+
15
+ unless msgid.blank?
16
+ pot_entry = GetText::POEntry.new(:msgctxt)
17
+ pot_entry.msgid = msgid
18
+ pot_entry.msgctxt = key.split('.', 2).last
19
+ #pot_entry.references = [ value[:locale_file_path] ]
20
+
21
+ pot_representation[ pot_entry.msgctxt, pot_entry.msgid ] = pot_entry
22
+ end
23
+ end
24
+
25
+ pot_representation.to_s
26
+ end
27
+
28
+ def get_yaml_data_from_po_data(po_data, target_locale)
29
+ parser = GetText::POParser.new
30
+ po_representation = GetText::PO.new
31
+ flat_translations = {}
32
+
33
+ parser.parse(po_data, po_representation)
34
+
35
+ po_representation.each do |po_entry|
36
+ flat_translations["#{target_locale}.#{po_entry.msgctxt}"] = po_entry.msgstr
37
+ end
38
+
39
+ translations = YAMLConversion.get_yaml_data_from_flat_translations(flat_translations)
40
+
41
+ return translations
42
+ end
43
+
44
+ # Shortcut methods
45
+
46
+ def get_flat_translations_for_locale(locale, yaml_file_paths = nil)
47
+ yaml_file_paths = I18n.load_path if yaml_file_paths.blank?
48
+
49
+ all_flat_translations = {}
50
+
51
+ yaml_file_paths.each do |yaml_file_path|
52
+ content = File.read(yaml_file_path)
53
+ translations = YAML::load(content)
54
+
55
+ if translations.has_key?(locale.to_s)
56
+ flat_translations = FlatHash.to_flat_hash(translations)
57
+ all_flat_translations.merge!(flat_translations)
58
+ end
59
+ end
60
+
61
+ return all_flat_translations
62
+ end
63
+
64
+ def get_flat_translations_for_yaml_file(file_path)
65
+ yaml_data = File.read(file_path)
66
+ return get_flat_translations_for_yaml_data(yaml_data)
67
+ end
68
+
69
+ def get_flat_translations_for_yaml_data(yaml_data)
70
+ translations = YAML::load(yaml_data)
71
+ return FlatHash.to_flat_hash(translations)
72
+ end
73
+
74
+ def get_yaml_data_from_flat_translations(flat_translations)
75
+ translations = FlatHash.to_hash(flat_translations)
76
+ return translations.to_yaml
77
+ end
78
+
79
+ end
80
+ end
81
+ end
metadata CHANGED
@@ -1,39 +1,131 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: translation
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aurelien Malisart
8
- - Michaël Hoste
8
+ - Michael Hoste
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-28 00:00:00.000000000 Z
12
+ date: 2014-07-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gettext
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - '>'
18
+ - - '>='
19
19
  - !ruby/object:Gem::Version
20
- version: 3.1.1
20
+ version: 3.1.2
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - '>'
25
+ - - '>='
26
26
  - !ruby/object:Gem::Version
27
- version: 3.1.1
27
+ version: 3.1.2
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rails
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: haml
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: slim
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
28
98
  description: translation.io connector
29
99
  email: contact@translation.io
30
100
  executables: []
31
101
  extensions: []
32
- extra_rdoc_files:
33
- - README.rdoc
102
+ extra_rdoc_files: []
34
103
  files:
104
+ - lib/translation/client/base_operation/create_new_mo_files_step.rb
105
+ - lib/translation/client/base_operation/dump_haml_gettext_keys_step.rb
106
+ - lib/translation/client/base_operation/dump_slim_gettext_keys_step.rb
107
+ - lib/translation/client/base_operation/save_new_po_files_step.rb
108
+ - lib/translation/client/base_operation/save_new_yaml_files_step.rb
109
+ - lib/translation/client/base_operation/save_special_yaml_files_step.rb
110
+ - lib/translation/client/base_operation.rb
111
+ - lib/translation/client/init_operation/cleanup_yaml_files_step.rb
112
+ - lib/translation/client/init_operation/create_yaml_po_files_step.rb
113
+ - lib/translation/client/init_operation/update_and_collect_po_files_step.rb
114
+ - lib/translation/client/init_operation/update_pot_file_step.rb
115
+ - lib/translation/client/init_operation.rb
116
+ - lib/translation/client/purge_operation.rb
117
+ - lib/translation/client/sync_operation/create_yaml_pot_file_step.rb
118
+ - lib/translation/client/sync_operation/update_and_collect_pot_file_step.rb
119
+ - lib/translation/client/sync_operation.rb
120
+ - lib/translation/client.rb
121
+ - lib/translation/config.rb
122
+ - lib/translation/controller.rb
123
+ - lib/translation/flat_hash.rb
124
+ - lib/translation/railtie.rb
125
+ - lib/translation/tasks.rb
126
+ - lib/translation/yaml_conversion.rb
35
127
  - lib/translation.rb
36
- - README.rdoc
128
+ - README.md
37
129
  homepage: http://rails.translation.io
38
130
  licenses:
39
131
  - MIT
@@ -54,8 +146,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
146
  version: '0'
55
147
  requirements: []
56
148
  rubyforge_project:
57
- rubygems_version: 2.1.5
149
+ rubygems_version: 2.0.0
58
150
  signing_key:
59
151
  specification_version: 4
60
152
  summary: translation.io connector
61
153
  test_files: []
154
+ has_rdoc: false
data/README.rdoc DELETED
@@ -1,20 +0,0 @@
1
- = Description
2
-
3
- ...
4
-
5
- = Basic Usage
6
-
7
- ...
8
-
9
- = Installation
10
-
11
- In your project's Gemfile :
12
-
13
- gem 'translation'
14
-
15
- = Credits
16
-
17
- This plugin in released under MIT license by Aurélien Malisart and Michaël Hoste (see MIT-LICENSE
18
- file).
19
-
20
- (c) http://rails.translation.io