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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d6ddb9f33eac9ad392124d3d42bc094e5b0f2c4
4
+ data.tar.gz: fca0db5616d9ff41a3d8bbbfd873974293831179
5
+ SHA512:
6
+ metadata.gz: fc1eceaa98d8982ce77098aa0010cb735d1905f5c3fd5fe234914013eaed133bd15ab95cb00a09f2461a8852b345a5753fa428eefc9edf7809d7d79051320e80
7
+ data.tar.gz: 3bded82ae6a10c8faa53ffef5f56981a168087f568884c73c2cb7905d5cd15d702f8afc89d0b21181e89d9b3f0682e8a277b6eb306b4d45b8931e2d4a61a62c4
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in c11n.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Inbeom Hwang
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # C11n
2
+
3
+ C11n enables you to collaborate on i18n for your apps easily using Google
4
+ Drive. It can upload locales in your project to Google Drive Spreadsheet and
5
+ download contents from Google Drive Spreadsheet and modify your source files
6
+ up to date.
7
+
8
+ Default conventions for Rails/Android/iOS application projects are supported.
9
+
10
+ ## Installation
11
+
12
+ C11n runs from command line. Install it via Rubygems as:
13
+
14
+ $ gem install c11n
15
+
16
+ You may need to install additional libraries c11n depends on:
17
+
18
+ $ gem install nokogiri # for parsing XML files in Android projects
19
+
20
+ ## Usage
21
+
22
+ You need to make your own configuration file for your projects. Example:
23
+
24
+ ---
25
+ :external:
26
+ :google_drive:
27
+ :email: [EMAIL ADDRESS OF YOUR GOOGLE ACCOUNT]
28
+ :password: [PASSWORD FOR YOUR GOOGLE ACCOUNT]
29
+ :spreadsheet_key: [KEY OF SPREADSHEET CONTAINS LOCALES FOR YOUR APPLICATION]
30
+ :worksheet_number: [0-BASED INDEX OF YOUR WORKSHEET]
31
+ :type: [TYPE OF YOUR PROJECT: android, ios, rails]
32
+ :root: [ROOT DIRECTORY OF YOUR PROJECT]
33
+ :default_locale: [DEFAULT LOCALE FOR YOUR PROJECT - ONLY FOR android]
34
+
35
+ After writing configuration file, you can set up a spreadsheet for your project
36
+ with `setup` task. You may skip this step if you have a spreadsheet already.
37
+
38
+ $ c11n -c [PATH TO CONFIG FILE] setup
39
+
40
+ After setting up the spreadsheet, you can move your i18n dat to/from google
41
+ drive using these commands:
42
+
43
+ $ c11n -c [PATH TO CONFIG FILE] push
44
+ $ c11n -c [PATH TO CONFIG FILE] pull
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'optparse'
6
+
7
+ require 'c11n'
8
+ require 'c11n/executable/runner'
9
+
10
+ options = {}
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = 'Usage: c11n [options]'
14
+
15
+ opts.on('-c', '--config [CONFIG_FILE]', 'Configuration file') do |config_file_path|
16
+ options[:config_file_path] = config_file_path
17
+ end
18
+ end.parse!
19
+
20
+ options[:task] = ARGV.shift
21
+
22
+ C11n::Executable::Runner.new(options).run
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'c11n/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'c11n'
8
+ gem.version = C11n::VERSION
9
+ gem.authors = ['Inbeom Hwang']
10
+ gem.email = ['hwanginbeom@gmail.com']
11
+ gem.description = %q{Imports/exports i18n translations from/to Google Drive Spreadsheet for easier collaboration.}
12
+ gem.summary = %q{Manage i18n for your projects using Google Drive.}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'google_drive'
21
+
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'nokogiri'
25
+ end
@@ -0,0 +1,8 @@
1
+ require 'c11n/version'
2
+ require 'c11n/configuration'
3
+
4
+ module C11n
5
+ def self.configure
6
+ yield C11n::Configuration.instance
7
+ end
8
+ end
@@ -0,0 +1,72 @@
1
+ module C11n
2
+ class Configuration
3
+ attr_reader :configurations, :externals
4
+
5
+ class NotConfigured < StandardError; end
6
+
7
+ def self.instance
8
+ @instance ||= new
9
+ end
10
+
11
+ def initialize
12
+ @configurations = {}
13
+ @externals = {}
14
+ end
15
+
16
+ def external(klass)
17
+ if block_given?
18
+ yield(@externals[klass] ||= Configuration.new)
19
+
20
+ @externals[klass]
21
+ else
22
+ @externals[klass] || raise(NotConfigured.new)
23
+ end
24
+ end
25
+
26
+ def [](key)
27
+ @configurations[key]
28
+ end
29
+
30
+ def []=(key, value)
31
+ @configurations[key.to_sym] = value
32
+ end
33
+
34
+ def to_hash
35
+ @configurations
36
+ end
37
+
38
+ def method_missing(method_name, *args, &block)
39
+ if method_name =~ /(.+)=$/
40
+ @configurations[$1.to_sym] = args.first
41
+ elsif value = @configurations[method_name.to_sym]
42
+ value
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def configure_with_hash(configurations)
49
+ configurations.each do |config_key, config_value|
50
+ @configurations[config_key.to_sym] = config_value
51
+ end
52
+ end
53
+
54
+ def load_from_hash(configurations)
55
+ load_external_configurations(configurations.delete(:external) || {})
56
+
57
+ configurations.each do |entry, configuration|
58
+ self[entry] = configuration
59
+ end
60
+ end
61
+
62
+ def load_external_configurations(externals)
63
+ externals.each do |external_entry, nested_configurations|
64
+ external(external_entry) do |external_config|
65
+ nested_configurations.each do |entry, configuration|
66
+ external_config[entry] = configuration
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,37 @@
1
+ require 'c11n/translations'
2
+
3
+ module C11n
4
+ module Conversion
5
+ # Deserializes translations in tabular form into Hash.
6
+ class ComposedKeyDeserializer
7
+ attr_accessor :table
8
+ attr_reader :translations
9
+
10
+ def initialize(table)
11
+ @table = table
12
+ @translations = {}
13
+ end
14
+
15
+ def deserialize
16
+ @table.each do |composed_key, value|
17
+ path = path_for(composed_key)
18
+ create_or_get_leaf_node_for(path)[path.last] = value
19
+ end
20
+
21
+ @translations
22
+ end
23
+
24
+ def create_or_get_leaf_node_for(path)
25
+ path[0..-2].inject(@translations) do |node, path_component|
26
+ node[path_component] ||= {}
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def path_for(key)
33
+ key.split('.').map(&:to_sym)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module C11n
2
+ module Conversion
3
+ class PlainDeserializer
4
+ attr_accessor :table
5
+ attr_reader :translations
6
+
7
+ def initialize(table)
8
+ @table = table
9
+ @translations = {}
10
+ end
11
+
12
+ def deserialize
13
+ @table.each do |key, value|
14
+ @translations[key] = value
15
+ end
16
+
17
+ @translations
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,51 @@
1
+ module C11n
2
+ module Conversion
3
+ class Serializer
4
+
5
+ class Pair
6
+ attr_accessor :key, :value
7
+
8
+ def initialize(key, value)
9
+ @key = key
10
+ @value = value
11
+ end
12
+
13
+ def to_a
14
+ [@key, @value]
15
+ end
16
+
17
+ def ==(another)
18
+ another.key == @key && another.value == @value
19
+ end
20
+ end
21
+
22
+ attr_accessor :translations
23
+ attr_reader :table
24
+
25
+ def initialize(translations)
26
+ @translations = translations
27
+ @table = nil
28
+ end
29
+
30
+ def serialize
31
+ @table ||= Hash[serialized_translations_for('', @translations).map(&:to_a)]
32
+ end
33
+
34
+ private
35
+
36
+ def serialized_translations_for(prefix, translations)
37
+ translations.map do |key, value|
38
+ if value.is_a?(Hash)
39
+ serialized_translations_for(child_prefix_for(prefix, key), value)
40
+ else
41
+ Pair.new(child_prefix_for(prefix, key), value)
42
+ end
43
+ end.flatten
44
+ end
45
+
46
+ def child_prefix_for(prefix, key)
47
+ prefix.empty? ? key : "#{prefix}.#{key}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,43 @@
1
+ require 'c11n/translations'
2
+ require 'c11n/synchronizer'
3
+
4
+ module C11n
5
+ module Executable
6
+ class Runner
7
+ def initialize(options = {})
8
+ @options = options
9
+ end
10
+
11
+ def run
12
+ load_config_file
13
+
14
+ case task
15
+ when 'push'
16
+ synchronizer.upload
17
+ when 'pull'
18
+ synchronizer.download
19
+ when 'setup'
20
+ synchronizer.setup
21
+ end
22
+ end
23
+
24
+ def task
25
+ @options[:task]
26
+ end
27
+
28
+ def config_hash
29
+ if @options[:config_file_path]
30
+ @config_hash ||= YAML.load(File.open(@options[:config_file_path]).read)
31
+ end
32
+ end
33
+
34
+ def load_config_file
35
+ C11n::Configuration.instance.load_from_hash(config_hash) if config_hash
36
+ end
37
+
38
+ def synchronizer
39
+ C11n::Synchronizer.new(configuration: C11n::Configuration.instance)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ require 'nokogiri'
2
+ require 'c11n/types'
3
+
4
+ module C11n
5
+ module Exporter
6
+ class Android
7
+ def initialize(options = {})
8
+ @path = options[:path]
9
+ end
10
+
11
+ def file
12
+ @file ||= File.new(@path, 'w')
13
+ end
14
+
15
+ def export(translations, locale)
16
+ builder = Nokogiri::XML::Builder.new(encoding: 'utf-8')
17
+ types = translations.types
18
+
19
+ root_node = builder.resources do |xml|
20
+ translations.translations_for(locale).each do |key, translation|
21
+ case types[key]
22
+ when C11n::Types::ARRAY
23
+ xml.send(:'string-array') do
24
+ translation.each do |item|
25
+ xml.item item
26
+ end
27
+ end
28
+ when C11n::Types::CDATA
29
+ xml.string { xml.cdata translation }
30
+ else
31
+ xml.string translation
32
+ end['name'] = key
33
+ end
34
+ end
35
+
36
+ file.print builder.to_xml
37
+ file.close
38
+ end
39
+ end
40
+ end
41
+ end