i18n-docs 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.md +8 -0
- data/README.md +69 -0
- data/Rakefile +17 -0
- data/i18n-docs.gemspec +19 -0
- data/lib/localch-i18n.rb +22 -0
- data/lib/localch_i18n/csv_to_yaml.rb +68 -0
- data/lib/localch_i18n/missing_keys_finder.rb +112 -0
- data/lib/localch_i18n/translation_file_export.rb +77 -0
- data/lib/localch_i18n/translations.rb +70 -0
- data/lib/tasks/store_translations.rake +57 -0
- data/tasks/test.rake +9 -0
- data/test/fixtures/config.yml +9 -0
- data/test/fixtures/de/header.yml +8 -0
- data/test/fixtures/en/header.yml +8 -0
- data/test/fixtures/minimal.csv +24 -0
- data/test/fixtures/test.csv +24 -0
- data/test/test_helper.rb +27 -0
- data/test/unit/csv_to_yaml_test.rb +137 -0
- data/test/unit/translation_file_export_test.rb +84 -0
- data/test/unit/translations_test.rb +62 -0
- metadata +140 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2012 Local.ch AG
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
# ![local.ch](http://www.local.ch/favicon.ico) local.ch i18n helpers
|
3
|
+
|
4
|
+
**i18n-docs** is a ruby gem that helps you to keep translations stored in Google Docs. This makes it easier for translators and project members to coordinate changes and updates. In addition, the standard features of Google Docs are super handy for this process: revision tracking, authorization, publishing, etc.
|
5
|
+
|
6
|
+
Although we at use it with Google Docs, it could be used with any CSV file.
|
7
|
+
|
8
|
+
## Features:
|
9
|
+
|
10
|
+
* download translations from multiple Google spreadsheets and store to YAML files
|
11
|
+
* import/export YAML files with a Google Spreadsheet
|
12
|
+
* check YAML files for missing translations (`rake i18n:find_missing_keys`)
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Add the GEM to your Rails project:
|
17
|
+
|
18
|
+
gem 'i18n-docs'
|
19
|
+
|
20
|
+
The rake task `i18n:import_translations` requires following configuration file in `config/translations.yml`:
|
21
|
+
|
22
|
+
files:
|
23
|
+
naviagion.yml: "https://docs.google.com/spreadsheet/pub?key=ab43...34f3&single=true&gid=0&output=csv"
|
24
|
+
forms.yml: "https://docs.google.com/spreadsheet/pub?key=0Ap...XveWc&single=true&gid=0&output=csv"
|
25
|
+
|
26
|
+
This defines which translations files should be created by exporting a Google Spreadsheet. The content of the Spreadsheet URL is stored to a file called e.g. `example1.yml` within folders `config/locales/en` and all other detected locales.
|
27
|
+
|
28
|
+
### Rake Tasks
|
29
|
+
|
30
|
+
Following Rake tasks are added by the GEM to your Rails project:
|
31
|
+
|
32
|
+
* rake i18n:export_translations
|
33
|
+
Export all language files to CSV files (only files contained in 'en' folder are considered)
|
34
|
+
* rake i18n:import_translations
|
35
|
+
Download translations from Google Spreadsheet and save them to YAML files.
|
36
|
+
* rake i18n:missing_keys
|
37
|
+
Find and list translation keys that do not exist in all locales
|
38
|
+
|
39
|
+
|
40
|
+
## Setup at Google Docs
|
41
|
+
|
42
|
+
* At Google Docs, create a new spreadsheet for your translations. The first column is the translation key, the other columns are the languages:
|
43
|
+
![screenshot](http://dl.dropbox.com/u/385855/Screenshots/bamr.png)
|
44
|
+
* use option `File -> Publish To the Web`
|
45
|
+
![screenshot](http://dl.dropbox.com/u/385855/Screenshots/2-lp.png)
|
46
|
+
* use **get a link** to generate a unique url that points to the CSV spreadsheet
|
47
|
+
![screenshot](http://dl.dropbox.com/u/385855/Screenshots/oom_.png)
|
48
|
+
* From now on you should only update translations in Google Docs and run `rake i18n:import_translations` in the application to get changes. You can also export your
|
49
|
+
|
50
|
+
|
51
|
+
## CHANGELOG
|
52
|
+
|
53
|
+
### 0.0.4
|
54
|
+
|
55
|
+
Update docs, license. Push to Rubygems.org.
|
56
|
+
|
57
|
+
### 0.0.3
|
58
|
+
|
59
|
+
* Open sourced: changed name and description.
|
60
|
+
|
61
|
+
### 0.0.2
|
62
|
+
|
63
|
+
* removed loading of `awesome_print` from the rake task. It was breaking
|
64
|
+
download
|
65
|
+
|
66
|
+
### Credits/License
|
67
|
+
|
68
|
+
This gem is sponsored by [local.ch](http://www.local.ch/). It is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License). If you're a ruby developer and want to work with us in Switzerland, please check out our [jobs page](http://local-ch.github.com/).
|
69
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'bundler/gem_tasks'
|
12
|
+
require 'localch-i18n'
|
13
|
+
|
14
|
+
task :default => [:test]
|
15
|
+
|
16
|
+
# Load tasks
|
17
|
+
Dir.glob('tasks/**/*.rake').each { |r| Rake.application.add_import r }
|
data/i18n-docs.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'i18n-docs'
|
3
|
+
s.version = '0.0.3'
|
4
|
+
s.date = '2012-04-20'
|
5
|
+
s.summary = "Maintain translations in Google Docs and export them to your Rails project."
|
6
|
+
s.description = "GEM providing helper scripts to manage i18n translations in Google Docs. Features: check YAML files for missing translations; export YAML files to CSV; download translations from multiple Google spreadsheets and store to YAML files"
|
7
|
+
s.authors = ["Georg Kunz", "Ivan Jovanovic", "Jeremy Seitz"]
|
8
|
+
s.email = 'jeremy.seitz@local.ch'
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.homepage = 'https://github.com/local-ch/i18n-docs'
|
11
|
+
|
12
|
+
# dependencies
|
13
|
+
s.add_dependency('bundler')
|
14
|
+
s.add_dependency('rake')
|
15
|
+
|
16
|
+
s.add_development_dependency('mocha')
|
17
|
+
s.add_development_dependency('awesome_print')
|
18
|
+
|
19
|
+
end
|
data/lib/localch-i18n.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# require 'rake'
|
2
|
+
|
3
|
+
# load rake tasks in case GEM is included within rails project
|
4
|
+
|
5
|
+
require 'csv'
|
6
|
+
require 'yaml'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'localch_i18n/missing_keys_finder'
|
9
|
+
require 'localch_i18n/csv_to_yaml'
|
10
|
+
require 'localch_i18n/translations'
|
11
|
+
require 'localch_i18n/translation_file_export'
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
if defined?(Rails)
|
16
|
+
class LocalchI18nTask < Rails::Railtie
|
17
|
+
rake_tasks do
|
18
|
+
Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
|
3
|
+
class CsvToYaml
|
4
|
+
|
5
|
+
attr_reader :input_file, :output_file, :locales, :translations
|
6
|
+
|
7
|
+
def initialize(input_file, output_file, locales = [])
|
8
|
+
@input_file = input_file
|
9
|
+
@output_file = File.basename(output_file)
|
10
|
+
@locales = locales.map(&:to_s)
|
11
|
+
|
12
|
+
# init translation hash
|
13
|
+
@translations = {}
|
14
|
+
@locales.each do |locale|
|
15
|
+
@translations[locale] = {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def write_files
|
21
|
+
@locales.each do |locale|
|
22
|
+
output_file_path = defined?(Rails) ? Rails.root.join('config', 'locales', locale, @output_file) : "#{locale}_#{@output_file}"
|
23
|
+
File.open(output_file_path, 'w') do |file|
|
24
|
+
final_translation_hash = {locale => @translations[locale]}
|
25
|
+
file.puts YAML::dump(final_translation_hash)
|
26
|
+
end
|
27
|
+
puts "File '#{@output_file}' for language '#{locale}' written to disc (#{output_file_path})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def process
|
33
|
+
CSV.foreach(@input_file, headers: true) do |row|
|
34
|
+
process_row(row.to_hash)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_row(row_hash)
|
39
|
+
key = row_hash.delete('key')
|
40
|
+
|
41
|
+
key_elements = key.split('.')
|
42
|
+
@locales.each do |locale|
|
43
|
+
raise "Locale missing for key #{key}! (locales in app: #{@locales} / locales in file: #{row_hash.keys.to_s})" if !row_hash.has_key?(locale)
|
44
|
+
store_translation(key_elements, locale, row_hash[locale])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def store_translation(keys, locale, value)
|
50
|
+
return nil if value.nil? # we don't store keys that don't have a valid value
|
51
|
+
# Google Spreadsheet does not export empty strings and therefore we use '_' as a replacement char.
|
52
|
+
value = '' if value == '_'
|
53
|
+
|
54
|
+
tree = keys[0...-1]
|
55
|
+
leaf = keys.last
|
56
|
+
data_hash = tree.inject(@translations[locale]) do |memo, k|
|
57
|
+
if memo.has_key?(k)
|
58
|
+
memo[k]
|
59
|
+
else
|
60
|
+
memo[k] = {}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
data_hash[leaf] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
|
2
|
+
module LocalchI18n
|
3
|
+
class MissingKeysFinder
|
4
|
+
def initialize(backend)
|
5
|
+
@backend = backend
|
6
|
+
self.load_config
|
7
|
+
self.load_translations
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns an array with all keys from all locales
|
11
|
+
def all_keys
|
12
|
+
I18n.backend.send(:translations).collect do |check_locale, translations|
|
13
|
+
collect_keys([], translations).sort
|
14
|
+
end.flatten.uniq
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_missing_keys
|
18
|
+
output_available_locales
|
19
|
+
output_unique_key_stats(all_keys)
|
20
|
+
|
21
|
+
missing_keys = {}
|
22
|
+
all_keys.each do |key|
|
23
|
+
|
24
|
+
I18n.available_locales.each do |locale|
|
25
|
+
|
26
|
+
skip = false
|
27
|
+
ls = locale.to_s
|
28
|
+
if !@yaml[ls].nil?
|
29
|
+
@yaml[ls].each do |re|
|
30
|
+
if key.match(re)
|
31
|
+
skip = true
|
32
|
+
break
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if !key_exists?(key, locale) && skip == false
|
38
|
+
if missing_keys[key]
|
39
|
+
missing_keys[key] << locale
|
40
|
+
else
|
41
|
+
missing_keys[key] = [locale]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
output_missing_keys(missing_keys)
|
48
|
+
return missing_keys
|
49
|
+
end
|
50
|
+
|
51
|
+
def output_available_locales
|
52
|
+
puts "#{I18n.available_locales.size} #{I18n.available_locales.size == 1 ? 'locale' : 'locales'} available: #{I18n.available_locales.join(', ')}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def output_missing_keys(missing_keys)
|
56
|
+
if missing_keys.size > 0
|
57
|
+
puts "#{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales:"
|
58
|
+
missing_keys.keys.sort.each do |key|
|
59
|
+
puts "'#{key}': Missing from #{missing_keys[key].collect(&:inspect).join(', ')}"
|
60
|
+
end
|
61
|
+
puts "\nERROR: #{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales."
|
62
|
+
else
|
63
|
+
puts "No keys are missing"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def output_unique_key_stats(keys)
|
68
|
+
number_of_keys = keys.size
|
69
|
+
puts "#{number_of_keys} #{number_of_keys == 1 ? 'unique key' : 'unique keys'} found."
|
70
|
+
end
|
71
|
+
|
72
|
+
def collect_keys(scope, translations)
|
73
|
+
full_keys = []
|
74
|
+
translations.to_a.each do |key, translations|
|
75
|
+
new_scope = scope.dup << key
|
76
|
+
if translations.is_a?(Hash)
|
77
|
+
full_keys += collect_keys(new_scope, translations)
|
78
|
+
else
|
79
|
+
full_keys << new_scope.join('.')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
return full_keys
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if key exists in the given locale
|
86
|
+
def key_exists?(key, locale)
|
87
|
+
I18n.locale = locale
|
88
|
+
I18n.translate(key, :raise => true)
|
89
|
+
return true
|
90
|
+
rescue I18n::MissingInterpolationArgument
|
91
|
+
return true
|
92
|
+
rescue I18n::MissingTranslationData
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
def load_translations
|
97
|
+
# Make sure we’ve loaded the translations
|
98
|
+
I18n.backend.send(:init_translations)
|
99
|
+
end
|
100
|
+
|
101
|
+
def load_config
|
102
|
+
@yaml = {}
|
103
|
+
begin
|
104
|
+
@yaml = YAML.load_file(File.join(Rails.root, 'config', 'ignore_missing_i18n_keys.yml'))
|
105
|
+
rescue => e
|
106
|
+
STDERR.puts "No ignore_missing_keys.yml config file."
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
class TranslationFileExport
|
3
|
+
|
4
|
+
attr_accessor :translations
|
5
|
+
|
6
|
+
def initialize(source_dir, source_file, output_dir, locales)
|
7
|
+
@source_dir = source_dir
|
8
|
+
@source_file = source_file
|
9
|
+
|
10
|
+
@output_file = File.join(output_dir, source_file.gsub('.yml', '.csv'))
|
11
|
+
@locales = locales.map {|l| l.to_s.downcase }
|
12
|
+
|
13
|
+
@translations = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def export
|
18
|
+
load_translations
|
19
|
+
write_to_csv
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def write_to_csv
|
24
|
+
main_locale = @locales.include?('en') ? 'en' : @locales.first
|
25
|
+
|
26
|
+
puts " #{@source_file}: write CSV to '#{@output_file}' \n\n"
|
27
|
+
|
28
|
+
CSV.open(@output_file, "wb") do |csv|
|
29
|
+
csv << (["key"] + @locales)
|
30
|
+
|
31
|
+
@translations[main_locale].keys.each do |key|
|
32
|
+
values = @locales.map do |locale|
|
33
|
+
@translations[locale][key]
|
34
|
+
end
|
35
|
+
csv << values.unshift(key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def load_translations
|
43
|
+
@locales.each do |locale|
|
44
|
+
translation_hash = load_language(locale)
|
45
|
+
@translations[locale] = flatten_translations_hash(translation_hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_language(locale)
|
50
|
+
|
51
|
+
puts " #{@source_file}: load translations for '#{locale}'"
|
52
|
+
|
53
|
+
input_file = File.join(@source_dir, locale, @source_file)
|
54
|
+
translations = {}
|
55
|
+
translations = YAML.load_file(input_file) if File.exists?(input_file)
|
56
|
+
translations[locale]
|
57
|
+
end
|
58
|
+
|
59
|
+
def flatten_translations_hash(translations, parent_key = [])
|
60
|
+
flat_hash = {}
|
61
|
+
|
62
|
+
translations.each do |key, t|
|
63
|
+
current_key = parent_key.dup << key
|
64
|
+
if t.is_a?(Hash)
|
65
|
+
# descend
|
66
|
+
flat_hash.merge!(flatten_translations_hash(t, current_key))
|
67
|
+
else
|
68
|
+
# leaf -> store as value for key
|
69
|
+
flat_hash[current_key.join('.')] = t
|
70
|
+
end
|
71
|
+
end
|
72
|
+
flat_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Order of method calls
|
3
|
+
# download_files
|
4
|
+
# store_translations
|
5
|
+
# clean_up
|
6
|
+
#
|
7
|
+
module LocalchI18n
|
8
|
+
class Translations
|
9
|
+
|
10
|
+
attr_accessor :locales, :tmp_folder, :config_file, :csv_files
|
11
|
+
|
12
|
+
def initialize(config_file = nil, tmp_folder = nil)
|
13
|
+
@config_file = config_file
|
14
|
+
@tmp_folder = tmp_folder
|
15
|
+
|
16
|
+
@csv_files = {}
|
17
|
+
|
18
|
+
load_config
|
19
|
+
load_locales
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_locales
|
23
|
+
@locales = []
|
24
|
+
@locales = I18n.available_locales if defined?(I18n)
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_config
|
28
|
+
@settings = {}
|
29
|
+
@settings = YAML.load_file(config_file) if File.exists?(config_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
def download_files
|
33
|
+
files = @settings['files']
|
34
|
+
files.each do |target_file, url|
|
35
|
+
# download file to tmp directory
|
36
|
+
tmp_file = File.basename(target_file).gsub('.yml', '.csv')
|
37
|
+
tmp_file = File.join(@tmp_folder, tmp_file)
|
38
|
+
download(url, tmp_file)
|
39
|
+
@csv_files[target_file] = tmp_file
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def store_translations
|
44
|
+
@csv_files.each do |target_file, csv_file|
|
45
|
+
converter = CsvToYaml.new(csv_file, target_file, @locales)
|
46
|
+
converter.process
|
47
|
+
converter.write_files
|
48
|
+
end
|
49
|
+
|
50
|
+
@csv_files
|
51
|
+
end
|
52
|
+
|
53
|
+
def clean_up
|
54
|
+
# remove all tmp files
|
55
|
+
@csv_files.each do |target_file, csv_file|
|
56
|
+
File.unlink(csv_file)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def download(url, destination_file)
|
61
|
+
puts "Download '#{url}' to '#{destination_file}'"
|
62
|
+
File.open(destination_file, 'w') do |dst|
|
63
|
+
dst.write(open(url).read)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
namespace :i18n do
|
3
|
+
|
4
|
+
desc "Find and list translation keys that do not exist in all locales"
|
5
|
+
task :missing_keys => :environment do
|
6
|
+
finder = LocalchI18n::MissingKeysFinder.new(I18n.backend)
|
7
|
+
finder.find_missing_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Download translations from Google Spreadsheet and save them to YAML files."
|
11
|
+
task :import_translations => :environment do
|
12
|
+
raise "'Rails' not found! Tasks can only run within a Rails application!" if !defined?(Rails)
|
13
|
+
|
14
|
+
config_file = Rails.root.join('config', 'translations.yml')
|
15
|
+
raise "No config file 'config/translations.yml' found." if !File.exists?(config_file)
|
16
|
+
|
17
|
+
tmp_dir = Rails.root.join('tmp')
|
18
|
+
|
19
|
+
translations = LocalchI18n::Translations.new(config_file, tmp_dir)
|
20
|
+
translations.download_files
|
21
|
+
translations.store_translations
|
22
|
+
translations.clean_up
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Export all language files to CSV files (only files contained in en folder are considered)"
|
27
|
+
task :export_translations => :environment do
|
28
|
+
raise "'Rails' not found! Tasks can only run within a Rails application!" if !defined?(Rails)
|
29
|
+
|
30
|
+
source_dir = Rails.root.join('config', 'locales')
|
31
|
+
output_dir = Rails.root.join('tmp')
|
32
|
+
locales = I18n.available_locales
|
33
|
+
|
34
|
+
input_files = Dir[File.join(source_dir, 'en', '*.yml')]
|
35
|
+
|
36
|
+
puts ""
|
37
|
+
puts " Detected locales: #{locales}"
|
38
|
+
puts " Detected files:"
|
39
|
+
input_files.each {|f| puts " * #{File.basename(f)}" }
|
40
|
+
|
41
|
+
puts ""
|
42
|
+
puts " Start exporting files:"
|
43
|
+
|
44
|
+
input_files.each do |file|
|
45
|
+
file = File.basename(file)
|
46
|
+
exporter = LocalchI18n::TranslationFileExport.new(source_dir, file, output_dir, locales)
|
47
|
+
exporter.export
|
48
|
+
end
|
49
|
+
|
50
|
+
puts ""
|
51
|
+
puts " CSV files can be removed safely after uploading them manually to Google Spreadsheet."
|
52
|
+
puts ""
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#
|
2
|
+
# Configure all spreadsheets you want to import:
|
3
|
+
#
|
4
|
+
# files:
|
5
|
+
# first.yml: http://www.google.com/spreadsheet/first.csv
|
6
|
+
# second.yml: http://www.google.com/spreadsheet/second.csv
|
7
|
+
#
|
8
|
+
files:
|
9
|
+
download.yml: "https://docs.google.com/spreadsheet/pub?key=0ApnemdIdiDXedEpiVFR1RkdWMDhnTTgtdzRJMWZMLUE&single=true&gid=0&output=csv"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
key,en,de
|
2
|
+
tel.browse_access_title,Largest cities,Grösste Städte
|
3
|
+
tel.browse_all_link,» Look-up all cities (A-Z),» Alle Orte A-Z
|
4
|
+
tel.extended.area_field_teaser,"",""
|
5
|
+
tel.extended.area_label,"Region, Canton","Region, Kanton"
|
6
|
+
tel.extended.city_field_teaser,"",""
|
7
|
+
tel.extended.city_label,"City, ZIP","Ort, PLZ"
|
8
|
+
tel.extended.company_field_teaser,"e.g. doctor, farmer","z.B. pizzaiolo, doktor"
|
9
|
+
tel.extended.company_label,Occupation,Beruf
|
10
|
+
tel.extended.name_field_teaser,"e.g. smith, phil*","z.B. meier, hans*"
|
11
|
+
tel.extended.name_label,"Name, First name","Name, Vorname"
|
12
|
+
tel.extended.phone_field_teaser,"",""
|
13
|
+
tel.extended.phone_label,Phone,Telefon
|
14
|
+
tel.extended.street_field_teaser,"",""
|
15
|
+
tel.extended.street_label,Street,Strasse
|
16
|
+
tel.homepage.meta.description,The official phone book for Switzerland by Swisscom Directories with over 6 million listings of private people and businesses in your vicinity.,Firmen und Privatpersonen in Ihrer Nähe einfach finden.
|
17
|
+
tel.homepage.meta.keywords,"phone book, city, Switzerland, directory, address, white pages","Telefonbuch, Schweiz, aktuell, Directories, Adresse"
|
18
|
+
tel.homepage.meta.title,Switzerland's official Phone Book by local.ch,Offizielles Telefonbuch der Schweiz von local.ch
|
19
|
+
tel.search_button,Find It,Finden
|
20
|
+
tel.search_for_people,Find names or numbers,Namen oder Nummern finden
|
21
|
+
tel.what_field_teaser,"e.g. Smith, Bar Odeon or 044 308 68 68","z.B. Müller, Bar Odeon oder 044 308 68 68"
|
22
|
+
tel.what_label,Who?,Wer?
|
23
|
+
tel.where_field_teaser,"e.g. Bern, 8004 or Hauptstrasse","z.B. Luzern, 5700 oder Bergstrasse"
|
24
|
+
tel.where_label,Where?,Wo?
|
@@ -0,0 +1,24 @@
|
|
1
|
+
key,en,de,fr,it
|
2
|
+
tel.browse_access_title,Largest cities,Grösste Städte,Villes Principales,Città principali
|
3
|
+
tel.browse_all_link,» Look-up all cities (A-Z),» Alle Orte A-Z,» Toutes les localités (A-Z),» Tutte le località A-Z
|
4
|
+
tel.extended.area_field_teaser,"","","",""
|
5
|
+
tel.extended.area_label,"Region, Canton","Region, Kanton","Région, Canton","Regione, Cantone"
|
6
|
+
tel.extended.city_field_teaser,"","","",""
|
7
|
+
tel.extended.city_label,"City, ZIP","Ort, PLZ","Localité, NPA","Località, Codice postale"
|
8
|
+
tel.extended.company_field_teaser,"e.g. doctor, farmer","z.B. pizzaiolo, doktor","par ex. médecin, agriculteur","per es. medico, agricoltore"
|
9
|
+
tel.extended.company_label,Occupation,Beruf,Profession,Professione
|
10
|
+
tel.extended.name_field_teaser,"e.g. smith, phil*","z.B. meier, hans*","par ex. berdoz, jean*","per es. rossi, robert*"
|
11
|
+
tel.extended.name_label,"Name, First name","Name, Vorname","Nom, Prénom","Cognome, Nome"
|
12
|
+
tel.extended.phone_field_teaser,"","","",""
|
13
|
+
tel.extended.phone_label,Phone,Telefon,Téléphone,Telefono
|
14
|
+
tel.extended.street_field_teaser,"","","",""
|
15
|
+
tel.extended.street_label,Street,Strasse,Rue,Via
|
16
|
+
tel.homepage.meta.description,The official phone book for Switzerland by Swisscom Directories with over 6 million listings of private people and businesses in your vicinity.,Firmen und Privatpersonen in Ihrer Nähe einfach finden.,Trouver simplement des entreprise et des personnes dans vos alentours.,Trovare con facilità privati ed aziende a voi vicine.
|
17
|
+
tel.homepage.meta.keywords,"phone book, city, Switzerland, directory, address, white pages","Telefonbuch, Schweiz, aktuell, Directories, Adresse","annuaire, Suisse, actuel, Directories, adresse","Elenco telefonico, Svizzera, attualità, Directories, indirizzo"
|
18
|
+
tel.homepage.meta.title,Switzerland's official Phone Book by local.ch,Offizielles Telefonbuch der Schweiz von local.ch,Annuaire téléphonique officiel sur local.ch,L’elenco telefonico della Svizzera su local.ch
|
19
|
+
tel.search_button,Find It,Finden,Trouver,Trovare
|
20
|
+
tel.search_for_people,Find names or numbers,Namen oder Nummern finden,Trouver un nom ou un numéro,Trova nomi e numeri
|
21
|
+
tel.what_field_teaser,"e.g. Smith, Bar Odeon or 044 308 68 68","z.B. Müller, Bar Odeon oder 044 308 68 68","par ex. Favre, Bar Cuba ou 044 308 68 68","per es. Rossi, Bar Incontro o 044 308 68 68"
|
22
|
+
tel.what_label,Who?,Wer?,Quoi ?,Cosa?
|
23
|
+
tel.where_field_teaser,"e.g. Bern, 8004 or Hauptstrasse","z.B. Luzern, 5700 oder Bergstrasse","par ex. Neuchâtel, 1630 ou rue de Tivoli","per es. Locarno, 6900 o via Molinazzo"
|
24
|
+
tel.where_label,Where?,Wo?,Où ?,Dove?
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'localch-i18n'
|
4
|
+
|
5
|
+
|
6
|
+
module TestHelper
|
7
|
+
|
8
|
+
def fixture_path
|
9
|
+
File.join(File.expand_path(File.dirname(__FILE__)), 'fixtures')
|
10
|
+
end
|
11
|
+
|
12
|
+
def tmp_dir
|
13
|
+
File.join(fixture_path, 'tmp')
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_tmp_dir
|
17
|
+
FileUtils::mkdir(tmp_dir) if ! File.exists?(tmp_dir)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_tmp_dir
|
21
|
+
FileUtils::rmtree(tmp_dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
# run test: ruby -I test/ -I lib/ test/unit/csv_to_yaml_test.rb
|
6
|
+
|
7
|
+
Rails = Struct.new(:dummy)
|
8
|
+
|
9
|
+
module UnitTests
|
10
|
+
|
11
|
+
class CsvToYamlTest < Test::Unit::TestCase
|
12
|
+
include TestHelper
|
13
|
+
|
14
|
+
def setup
|
15
|
+
create_tmp_dir
|
16
|
+
|
17
|
+
@input_file = File.join(fixture_path, 'minimal.csv')
|
18
|
+
@output_file = File.join(tmp_dir, 'test.yml')
|
19
|
+
@locales = ['de', 'en']
|
20
|
+
|
21
|
+
Rails.stubs(:root).returns(stub(:join => @output_file))
|
22
|
+
|
23
|
+
@csv_to_yaml = LocalchI18n::CsvToYaml.new(@input_file, @output_file, @locales)
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown
|
27
|
+
remove_tmp_dir
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_process_row
|
31
|
+
row1 = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => 'Telefonbuch der Schweiz'}
|
32
|
+
row2 = {'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen'}
|
33
|
+
@csv_to_yaml.process_row(row1)
|
34
|
+
@csv_to_yaml.process_row(row2)
|
35
|
+
|
36
|
+
translations = @csv_to_yaml.translations
|
37
|
+
assert_equal 'Telefonbuch der Schweiz', translations['de']['homepage']['meta']['title']
|
38
|
+
assert_equal 'Willkommen', translations['de']['homepage']['welcome']
|
39
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
40
|
+
assert_equal 'Welcome', translations['en']['homepage']['welcome']
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_row_containing_non_locale_columns
|
44
|
+
row = {'key' => 'homepage.title', 'en' => "We are the Phonebook", 'de' => 'Test DE', 'comment' => "Test comment"}
|
45
|
+
@csv_to_yaml.process_row(row)
|
46
|
+
|
47
|
+
translations = @csv_to_yaml.translations
|
48
|
+
assert_equal 'We are the Phonebook', translations['en']['homepage']['title']
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def test_empty_string_replacement_value
|
53
|
+
# As Google Spreadsheet does not export empty cells we use '_' as a fake whitespace which
|
54
|
+
# we replace with an empty string during CVS2YAML conversion.
|
55
|
+
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => '_'}
|
56
|
+
@csv_to_yaml.process_row(row)
|
57
|
+
|
58
|
+
translations = @csv_to_yaml.translations
|
59
|
+
assert_equal '', translations['de']['homepage']['meta']['title']
|
60
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def test_empty_string_value
|
65
|
+
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => ''}
|
66
|
+
@csv_to_yaml.process_row(row)
|
67
|
+
|
68
|
+
translations = @csv_to_yaml.translations
|
69
|
+
assert_equal '', translations['de']['homepage']['meta']['title']
|
70
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def test_space_value
|
75
|
+
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => ' '}
|
76
|
+
@csv_to_yaml.process_row(row)
|
77
|
+
|
78
|
+
translations = @csv_to_yaml.translations
|
79
|
+
assert_equal ' ', translations['de']['homepage']['meta']['title']
|
80
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_nil_value
|
84
|
+
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil}
|
85
|
+
@csv_to_yaml.process_row(row)
|
86
|
+
|
87
|
+
translations = @csv_to_yaml.translations
|
88
|
+
assert_equal({}, translations['de'])
|
89
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def test_nil_value_deep_structure
|
94
|
+
row1 = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil}
|
95
|
+
row2 = {'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen'}
|
96
|
+
@csv_to_yaml.process_row(row1)
|
97
|
+
@csv_to_yaml.process_row(row2)
|
98
|
+
|
99
|
+
translations = @csv_to_yaml.translations
|
100
|
+
assert_nil translations['de']['meta']
|
101
|
+
assert_equal 'Willkommen', translations['de']['homepage']['welcome']
|
102
|
+
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def test_store_translations
|
107
|
+
keys = ['homepage', 'meta', 'title']
|
108
|
+
@csv_to_yaml.store_translation(keys, 'de', 'Telefonbuch der Schweiz')
|
109
|
+
|
110
|
+
translations = @csv_to_yaml.translations
|
111
|
+
assert_equal 'Telefonbuch der Schweiz', translations['de']['homepage']['meta']['title']
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_process
|
115
|
+
@locales.each do |locale|
|
116
|
+
assert_empty @csv_to_yaml.translations[locale], "expected translation hash for locale '#{locale}' to be empty"
|
117
|
+
end
|
118
|
+
|
119
|
+
@csv_to_yaml.process
|
120
|
+
|
121
|
+
@locales.each do |locale|
|
122
|
+
assert @csv_to_yaml.translations[locale]['tel']['extended']['company_label']
|
123
|
+
assert @csv_to_yaml.translations[locale]['tel']['extended']['company_label'].is_a?(String)
|
124
|
+
assert @csv_to_yaml.translations[locale]['tel']['search_button']
|
125
|
+
assert @csv_to_yaml.translations[locale]['tel']['search_button'].is_a?(String)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_write_files
|
130
|
+
assert !File.exists?(@output_file)
|
131
|
+
@csv_to_yaml.process
|
132
|
+
@csv_to_yaml.write_files
|
133
|
+
assert File.exists?(@output_file)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
require 'awesome_print'
|
5
|
+
|
6
|
+
# run test: ruby -I test/ -I lib/ test/unit/locale_builder_test.rb
|
7
|
+
|
8
|
+
module UnitTests
|
9
|
+
class TranslationFileExportTest < Test::Unit::TestCase
|
10
|
+
include TestHelper
|
11
|
+
|
12
|
+
def setup
|
13
|
+
create_tmp_dir
|
14
|
+
|
15
|
+
source_dir = fixture_path
|
16
|
+
source_file = 'header.yml'
|
17
|
+
output_dir = tmp_dir
|
18
|
+
locales = ['en', 'de']
|
19
|
+
|
20
|
+
@exporter = LocalchI18n::TranslationFileExport.new(source_dir, source_file, output_dir, locales)
|
21
|
+
@output_file = File.join(output_dir, 'header.csv')
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
remove_tmp_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def test_export
|
30
|
+
assert !File.exists?(@output_file)
|
31
|
+
@exporter.export
|
32
|
+
assert File.exists?(@output_file), "Expected to have a CSV file written"
|
33
|
+
end
|
34
|
+
|
35
|
+
def dtest_load_language
|
36
|
+
translations = @exporter.load_language('de')
|
37
|
+
|
38
|
+
assert translations, "Expected to return a hash with translations"
|
39
|
+
assert_equal translations['header']['search'], "Finden"
|
40
|
+
end
|
41
|
+
|
42
|
+
def dtest_flatten_translations_hash
|
43
|
+
translation_hash = {'a' => {
|
44
|
+
'I' => '1',
|
45
|
+
'II' => '2',
|
46
|
+
'III' => {
|
47
|
+
'Z' => '3'
|
48
|
+
}
|
49
|
+
},
|
50
|
+
'b' => '4'
|
51
|
+
}
|
52
|
+
|
53
|
+
flat = @exporter.flatten_translations_hash(translation_hash, [])
|
54
|
+
assert_equal '1', flat['a.I']
|
55
|
+
assert_equal '2', flat['a.II']
|
56
|
+
assert_equal '3', flat['a.III.Z']
|
57
|
+
assert_equal '4', flat['b']
|
58
|
+
end
|
59
|
+
|
60
|
+
def dtest_load_translations
|
61
|
+
assert_empty @exporter.translations
|
62
|
+
@exporter.load_translations
|
63
|
+
|
64
|
+
assert @exporter.translations['de']
|
65
|
+
assert @exporter.translations['en']
|
66
|
+
assert_equal 'Finden', @exporter.translations['de']['header.search']
|
67
|
+
assert_equal 'Telefonbuch', @exporter.translations['de']['header.phonebook']
|
68
|
+
assert_equal '(c) local.ch', @exporter.translations['de']['copyright']
|
69
|
+
end
|
70
|
+
|
71
|
+
def dtest_write_to_csv
|
72
|
+
@exporter.translations = {'de' => {'numbers.one' => 'eins'},
|
73
|
+
'en' => {'numbers.one' => 'one'}}
|
74
|
+
|
75
|
+
@exporter.write_to_csv
|
76
|
+
|
77
|
+
output = File.read(@output_file)
|
78
|
+
assert_match /^key,(en|de|,){3}$/, output
|
79
|
+
assert_match /^numbers.one,(one|eins|,){3}$/, output
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
# run test: ruby -I test/ -I lib/ test/unit/locale_builder_test.rb
|
5
|
+
|
6
|
+
Rails = Struct.new(:dummy)
|
7
|
+
|
8
|
+
module UnitTests
|
9
|
+
class TranslationsTest < Test::Unit::TestCase
|
10
|
+
include TestHelper
|
11
|
+
|
12
|
+
def setup
|
13
|
+
create_tmp_dir
|
14
|
+
|
15
|
+
config_file = File.join(fixture_path, 'config.yml')
|
16
|
+
@translations = LocalchI18n::Translations.new(config_file, tmp_dir)
|
17
|
+
@translations.tmp_folder = tmp_dir
|
18
|
+
@translations.locales = ['de', 'en']
|
19
|
+
|
20
|
+
@fixture_file = File.join(fixture_path, 'minimal.csv')
|
21
|
+
@tmp_file = File.join(tmp_dir, 'downloaded.csv')
|
22
|
+
@output_file = File.join(tmp_dir, 'test.yml')
|
23
|
+
|
24
|
+
Rails.stubs(:root).returns(stub(:join => @output_file))
|
25
|
+
end
|
26
|
+
|
27
|
+
def teardown
|
28
|
+
remove_tmp_dir
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_download
|
32
|
+
assert !File.exists?(@tmp_file)
|
33
|
+
@translations.download("https://docs.google.com/spreadsheet/pub?key=0ApnemdIdiDXedEpiVFR1RkdWMDhnTTgtdzRJMWZMLUE&single=true&gid=0&output=csv", @tmp_file)
|
34
|
+
assert File.exists?(@tmp_file), "Expected to have downloaded Google Spreadsheet to '#{@tmp_file}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_cleanup
|
38
|
+
@translations.csv_files = {'dummy.yml' => @tmp_file}
|
39
|
+
File.open(@tmp_file, "w") {}
|
40
|
+
assert File.exists?(@tmp_file)
|
41
|
+
@translations.clean_up
|
42
|
+
assert !File.exists?(@tmp_file), "Expected to delete file"
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_store_translations
|
46
|
+
assert !File.exists?(@output_file)
|
47
|
+
|
48
|
+
@translations.csv_files = {@output_file => @fixture_file}
|
49
|
+
@translations.store_translations
|
50
|
+
|
51
|
+
assert File.exists?(@output_file)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_download_files
|
55
|
+
expected_file = File.join(tmp_dir, 'download.csv')
|
56
|
+
assert !File.exists?(expected_file)
|
57
|
+
@translations.download_files
|
58
|
+
assert File.exists?(expected_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: i18n-docs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Georg Kunz
|
9
|
+
- Ivan Jovanovic
|
10
|
+
- Jeremy Seitz
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-04-20 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rake
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: awesome_print
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
type: :development
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
description: ! 'GEM providing helper scripts to manage i18n translations in Google
|
81
|
+
Docs. Features: check YAML files for missing translations; export YAML files to
|
82
|
+
CSV; download translations from multiple Google spreadsheets and store to YAML files'
|
83
|
+
email: jeremy.seitz@local.ch
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- .gitignore
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.md
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- i18n-docs.gemspec
|
94
|
+
- lib/localch-i18n.rb
|
95
|
+
- lib/localch_i18n/csv_to_yaml.rb
|
96
|
+
- lib/localch_i18n/missing_keys_finder.rb
|
97
|
+
- lib/localch_i18n/translation_file_export.rb
|
98
|
+
- lib/localch_i18n/translations.rb
|
99
|
+
- lib/tasks/store_translations.rake
|
100
|
+
- tasks/test.rake
|
101
|
+
- test/fixtures/config.yml
|
102
|
+
- test/fixtures/de/header.yml
|
103
|
+
- test/fixtures/en/header.yml
|
104
|
+
- test/fixtures/minimal.csv
|
105
|
+
- test/fixtures/test.csv
|
106
|
+
- test/test_helper.rb
|
107
|
+
- test/unit/csv_to_yaml_test.rb
|
108
|
+
- test/unit/translation_file_export_test.rb
|
109
|
+
- test/unit/translations_test.rb
|
110
|
+
homepage: https://github.com/local-ch/i18n-docs
|
111
|
+
licenses: []
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: 2117717396188885452
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
hash: 2117717396188885452
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 1.8.19
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: Maintain translations in Google Docs and export them to your Rails project.
|
140
|
+
test_files: []
|