c11n 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,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