i18n-docs 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ .rake_tasks~
7
+ doc/yardoc
8
+ .yardoc
9
+ tmp/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in localch.gemspec
4
+ gemspec
@@ -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
+
@@ -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
+
@@ -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 }
@@ -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
@@ -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
+
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ # Test::Unit Tests
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/*_test.rb'
7
+ test.verbose = true
8
+ end
9
+
@@ -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,8 @@
1
+ de:
2
+ header:
3
+ search: Finden
4
+ phonebook: Telefonbuch
5
+ the_yellow_pages: Die Gelben Seiten
6
+ property: Immobilien
7
+ map: Karte
8
+ copyright: (c) local.ch
@@ -0,0 +1,8 @@
1
+ en:
2
+ header:
3
+ search: Find
4
+ phonebook: Phone Book
5
+ the_yellow_pages: The Yellow Pages
6
+ property: Property
7
+ map: Map
8
+ copyright: (c) local.ch
@@ -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?
@@ -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: []