c11n 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +6 -0
- data/bin/c11n +22 -0
- data/c11n.gemspec +25 -0
- data/lib/c11n.rb +8 -0
- data/lib/c11n/configuration.rb +72 -0
- data/lib/c11n/conversion/composed_key_deserializer.rb +37 -0
- data/lib/c11n/conversion/plain_deserializer.rb +21 -0
- data/lib/c11n/conversion/serializer.rb +51 -0
- data/lib/c11n/executable/runner.rb +43 -0
- data/lib/c11n/exporter/android.rb +41 -0
- data/lib/c11n/exporter/google_drive.rb +40 -0
- data/lib/c11n/exporter/ios.rb +21 -0
- data/lib/c11n/exporter/yaml.rb +26 -0
- data/lib/c11n/external/google_drive_driver.rb +64 -0
- data/lib/c11n/importer/android.rb +41 -0
- data/lib/c11n/importer/google_drive.rb +47 -0
- data/lib/c11n/importer/ios.rb +36 -0
- data/lib/c11n/importer/yaml.rb +42 -0
- data/lib/c11n/synchronizer.rb +58 -0
- data/lib/c11n/synchronizer/android.rb +50 -0
- data/lib/c11n/synchronizer/ios.rb +44 -0
- data/lib/c11n/synchronizer/rails.rb +41 -0
- data/lib/c11n/table.rb +131 -0
- data/lib/c11n/translations.rb +49 -0
- data/lib/c11n/types.rb +6 -0
- data/lib/c11n/version.rb +3 -0
- data/spec/c11n/configuration_spec.rb +61 -0
- data/spec/c11n/conversion/composed_key_deserializer_spec.rb +31 -0
- data/spec/c11n/conversion/serializer_spec.rb +32 -0
- data/spec/c11n/table_spec.rb +18 -0
- data/spec/c11n/translations_spec.rb +41 -0
- data/spec/spec_helper.rb +5 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/bin/c11n
ADDED
@@ -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
|
data/c11n.gemspec
ADDED
@@ -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
|
data/lib/c11n.rb
ADDED
@@ -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
|