c11n 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'c11n/external/google_drive_driver'
2
+ require 'c11n/types'
3
+
4
+ module C11n
5
+ module Exporter
6
+ class GoogleDrive
7
+ def initialize(options = {})
8
+ @driver = C11n::External::GoogleDriveDriver.new(options)
9
+ end
10
+
11
+ def table
12
+ @table ||= C11n::Table.new(@driver.table)
13
+ end
14
+
15
+ def export(translations, locale)
16
+ translations.translations_for(locale).each do |key, translation|
17
+ serialized_translation = serialize(translation, translations.types[key])
18
+ table.set(locale, key, serialized_translation, type: translations.types[key], category: translations.categories[key])
19
+ end
20
+
21
+ export_table(table)
22
+ end
23
+
24
+ def export_table(table)
25
+ @driver.write_table(table.rows)
26
+ end
27
+
28
+ private
29
+
30
+ def serialize(value, type = nil)
31
+ case type
32
+ when C11n::Types::ARRAY
33
+ value.join("\n\n")
34
+ else
35
+ value
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module C11n
2
+ module Exporter
3
+ class Ios
4
+ def initialize(options = {})
5
+ @path = options[:path]
6
+ end
7
+
8
+ def export(translations, locale)
9
+ file.print(translations.translations_for(locale).map do |key, translation|
10
+ "\"#{key}\" = \"#{translation}\";"
11
+ end.join("\n"))
12
+
13
+ file.close
14
+ end
15
+
16
+ def file
17
+ @file ||= File.new(@path, 'w')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ require 'c11n/conversion/composed_key_deserializer'
2
+
3
+ module C11n
4
+ module Exporter
5
+ class Yaml
6
+ def initialize(options = {})
7
+ @base_path = options[:base_path]
8
+ @path = options[:path] unless @base_path
9
+ end
10
+
11
+ def path_for(locale)
12
+ @path || "#{@base_path}/#{locale}.yml"
13
+ end
14
+
15
+ def file
16
+ @file ||= File.new(@path, 'w')
17
+ end
18
+
19
+ def export(translations, locale)
20
+ deserialized_translations = C11n::Conversion::ComposedKeyDeserializer.new(translations.translations_for(locale)).deserialize
21
+ file.print(YAML.dump(locale => deserialized_translations))
22
+ file.close
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ require 'google_drive'
2
+
3
+ module C11n
4
+ module External
5
+ class GoogleDriveDriver
6
+ attr_accessor :email, :password, :spreadsheet_key, :worksheet_number
7
+
8
+ def initialize(options = {})
9
+ load_from_options(options)
10
+ load_configuration
11
+ end
12
+
13
+ def load_from_options(options)
14
+ @email = options[:email]
15
+ @password = options[:password]
16
+ @spreadsheet_key = options[:spreadsheet_key]
17
+ @worksheet_number = options[:worksheet_number]
18
+ end
19
+
20
+ def load_configuration
21
+ @email ||= C11n::Configuration.instance.external(:google_drive)[:email]
22
+ @password ||= C11n::Configuration.instance.external(:google_drive)[:password]
23
+ @spreadsheet_key ||= C11n::Configuration.instance.external(:google_drive)[:spreadsheet_key]
24
+ @worksheet_number ||= C11n::Configuration.instance.external(:google_drive)[:worksheet_number]
25
+ end
26
+
27
+ def connection
28
+ @connection ||= ::GoogleDrive.login(@email, @password)
29
+ end
30
+
31
+ def worksheet
32
+ @worksheet ||= spreadsheet.worksheets[@worksheet_number]
33
+ end
34
+
35
+ def spreadsheet
36
+ @spreadsheet ||= connection.spreadsheet_by_key(@spreadsheet_key)
37
+ end
38
+
39
+ def table
40
+ raw_table = []
41
+
42
+ for row in 1..worksheet.num_rows
43
+ raw_table[row - 1] ||= []
44
+
45
+ for col in 1..worksheet.num_cols
46
+ raw_table[row - 1][col - 1] ||= worksheet[row, col]
47
+ end
48
+ end
49
+
50
+ raw_table
51
+ end
52
+
53
+ def write_table(table)
54
+ table.each_with_index do |row, row_index|
55
+ row.each_with_index do |column, column_index|
56
+ worksheet[row_index + 1, column_index + 1] = column if column
57
+ end
58
+ end
59
+
60
+ worksheet.save
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ require 'nokogiri'
2
+ require 'c11n/types'
3
+
4
+ module C11n
5
+ module Importer
6
+ class Android
7
+ attr_reader :types, :categories
8
+
9
+ def initialize(options = {})
10
+ @path = options[:path]
11
+ @locale = options[:locale]
12
+ @types = {}
13
+ @categories = {}
14
+ end
15
+
16
+ def file
17
+ File.open(@path, 'r')
18
+ end
19
+
20
+ def document
21
+ Nokogiri::XML(file)
22
+ end
23
+
24
+ def import
25
+ { @locale.to_sym => to_hash }
26
+ end
27
+
28
+ def to_hash
29
+ document.at_xpath('resources').elements.inject({}) do |result, element|
30
+ if element.name == 'string'
31
+ @types[element['name']] = C11n::Types::CDATA if element.children.any? { |child| child.cdata? }
32
+ result.merge(element['name'] => element.text.strip)
33
+ elsif element.name == 'string-array'
34
+ @types[element['name']] = C11n::Types::ARRAY
35
+ result.merge(element['name'] => element.elements.map(&:text))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ require 'c11n/external/google_drive_driver'
2
+ require 'c11n/table'
3
+
4
+ module C11n
5
+ module Importer
6
+ class GoogleDrive
7
+ def initialize(options = {})
8
+ @driver = C11n::External::GoogleDriveDriver.new(options)
9
+ end
10
+
11
+ def table
12
+ @table ||= C11n::Table.new(@driver.table)
13
+ end
14
+
15
+ def import
16
+ deserialized = {}
17
+
18
+ table.to_hash.each do |locale, translations|
19
+ deserialized[locale] = translations.inject({}) do |result, key_and_translation|
20
+ result.merge key_and_translation.first => deserialize(key_and_translation.last, types[key_and_translation.first])
21
+ end
22
+ end
23
+
24
+ deserialized
25
+ end
26
+
27
+ def categories
28
+ {}
29
+ end
30
+
31
+ def types
32
+ table.types
33
+ end
34
+
35
+ private
36
+
37
+ def deserialize(value, type = nil)
38
+ case type
39
+ when C11n::Types::ARRAY
40
+ value.split("\n\n")
41
+ else
42
+ value
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ module C11n
2
+ module Importer
3
+ class Ios
4
+ attr_reader :types, :categories
5
+
6
+ def initialize(options = {})
7
+ @path = options[:path]
8
+ @locale = options[:locale]
9
+ @types = {}
10
+ @categories = {}
11
+ end
12
+
13
+ def import
14
+ result = {}
15
+
16
+ file.each_line do |line|
17
+ result.merge parse(line)
18
+ end
19
+
20
+ result
21
+ end
22
+
23
+ def file
24
+ File.open(@path, 'r')
25
+ end
26
+
27
+ private
28
+
29
+ def parse(line)
30
+ matched_data = line.scan(/"([^"]+)"\s*=\s*"([^"]*)"/).first
31
+
32
+ matched_data && { matched_data.first => matched_data.last }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ require 'yaml'
2
+ require 'c11n/conversion/serializer'
3
+ require 'c11n/types'
4
+
5
+ module C11n
6
+ module Importer
7
+ class Yaml
8
+ def initialize(options = {})
9
+ @base_path = options[:base_path]
10
+ @path = options[:path]
11
+ @types = {}
12
+ end
13
+
14
+ def path_for(locale)
15
+ @path || "#{@base_path}/#{locale}.yml"
16
+ end
17
+
18
+ def file
19
+ File.open(@path, 'r')
20
+ end
21
+
22
+ def import
23
+ raw_yaml = YAML.load(file.read)
24
+ translations = C11n::Conversion::Serializer.new(raw_yaml.values.first).serialize
25
+
26
+ translations.each do |key, translation|
27
+ @types[key] = C11n::Types::ARRAY if translation.is_a?(Array)
28
+ end
29
+
30
+ { raw_yaml.keys.first => translations }
31
+ end
32
+
33
+ def categories
34
+ {}
35
+ end
36
+
37
+ def types
38
+ @types
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ require 'c11n/configuration'
2
+ require 'c11n/translations'
3
+ require 'c11n/importer/google_drive'
4
+ require 'c11n/exporter/google_drive'
5
+ require 'c11n/synchronizer/ios'
6
+ require 'c11n/synchronizer/android'
7
+ require 'c11n/synchronizer/rails'
8
+
9
+ module C11n
10
+ class Synchronizer
11
+ def project
12
+ @project ||= case @configuration.type
13
+ when 'ios'
14
+ C11n::Synchronizer::Ios.new(@configuration.to_hash)
15
+ when 'android'
16
+ C11n::Synchronizer::Android.new(@configuration.to_hash)
17
+ when 'rails'
18
+ C11n::Synchronizer::Rails.new(@configuration.to_hash)
19
+ end
20
+ end
21
+
22
+ def initialize(options = {})
23
+ @configuration = options[:configuration]
24
+ @translations = C11n::Translations.new
25
+ end
26
+
27
+ def google_drive_importer
28
+ @google_drive_importer ||= C11n::Importer::GoogleDrive.new
29
+ end
30
+
31
+ def google_drive_exporter
32
+ @google_drive_exporter ||= C11n::Exporter::GoogleDrive.new
33
+ end
34
+
35
+ def upload
36
+ project.importers.each do |locale, importer|
37
+ @translations.import_with(importer)
38
+ @translations.export_with(google_drive_exporter, locale)
39
+ end
40
+ end
41
+
42
+ def download
43
+ @translations.import_with(google_drive_importer)
44
+
45
+ project.exporters.each do |locale, exporter|
46
+ @translations.export_with(exporter, locale)
47
+ end
48
+ end
49
+
50
+ def setup
51
+ schema = [C11n::Table::CATEGORY_COLUMN, C11n::Table::KEY_COLUMN, C11n::Table::TYPE_COLUMN] + project.importers.keys.map(&:to_s)
52
+
53
+ table_with_schema = C11n::Table.new([schema])
54
+
55
+ google_drive_exporter.export_table(table_with_schema)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require 'c11n/importer/android'
2
+ require 'c11n/exporter/android'
3
+
4
+ module C11n
5
+ class Synchronizer
6
+ class Android
7
+ RESOURCES = 'res'
8
+ BASE_PATTERN = /^values-?/
9
+ FILE_NAME = 'strings.xml'
10
+
11
+ def initialize(options = {})
12
+ @project_root = options[:root]
13
+ @default_locale = options[:default_locale]
14
+ end
15
+
16
+ def importers
17
+ @importers ||= Hash[localization_files.map do |locale, file_path|
18
+ [locale, C11n::Importer::Android.new(path: file_path, locale: locale)]
19
+ end]
20
+ end
21
+
22
+ def exporters
23
+ @exporters ||= Hash[localization_files.map do |locale, file_path|
24
+ [locale, C11n::Exporter::Android.new(path: file_path)]
25
+ end]
26
+ end
27
+
28
+ def localization_files
29
+ @localization_files ||= localization_directories.inject({}) do |files, localization_directory|
30
+ locale = localization_directory.gsub(BASE_PATTERN, '')
31
+ path = File.join(@project_root, RESOURCES, localization_directory, FILE_NAME)
32
+
33
+ files.merge (locale.empty? ? @default_locale : locale.to_sym) => path
34
+ end
35
+ end
36
+
37
+ def localization_directories
38
+ @localization_directories ||= Dir.new(File.join(@project_root, RESOURCES)).entries.select { |entry| entry =~ BASE_PATTERN }.select do |entry|
39
+ Dir.new(File.join(@project_root, RESOURCES, entry)).entries.include?(FILE_NAME)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def locale_for(directory_name)
46
+ directory_name == BASE_NAME ? @default_locale.to_sym : directory_name.gsub("#{BASE_NAME}-", '').to_sym
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ require 'c11n/importer/ios'
2
+ require 'c11n/exporter/ios'
3
+
4
+ module C11n
5
+ class Synchronizer
6
+ class Ios
7
+ SUPPORT_FILES = 'support files'
8
+ LOCALIZABLE = 'Localizable.strings'
9
+
10
+ def initiailze(options = {})
11
+ @project_root = options[:root]
12
+ end
13
+
14
+ def importers
15
+ @importers ||= Hash[localization_files.map do |locale, file_path|
16
+ [locale, C11n::Importer::Ios.new(path: file_path, locale: locale)]
17
+ end]
18
+ end
19
+
20
+ def exporters
21
+ @exporters ||= Hash[localization_files.map do |locale|
22
+ [locale, C11n::Exporter::Ios.new(path: file_path)]
23
+ end]
24
+ end
25
+
26
+ def localization_files
27
+ @localization_files ||= localization_directories.inject({}) do |files, localization_directory|
28
+ locale = localization_directory.gsub(/\.lproj$/, '').to_sym
29
+ path = File.join(@project_root, SUPPORT_FILES, localization_directory, LOCALIZABLE)
30
+
31
+ files.merge locale => path
32
+ end
33
+ end
34
+
35
+ def locales
36
+ localization_directories.map { |directory| directory.gsub(/\.lproj$/, '') }.map(&:to_sym)
37
+ end
38
+
39
+ def localization_directories
40
+ @localization_directories ||= Dir.new(File.join(@project_root, SUPPORT_FILES)).entries.select { |entry| entry =~ /lproj$/ }
41
+ end
42
+ end
43
+ end
44
+ end