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.
- 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
|