i18n-docs 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +4 -0
- data/.github/workflows/ci.yml +21 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +11 -0
- data/.rubocop_todo.yml +50 -0
- data/Gemfile +1 -1
- data/README.md +36 -1
- data/Rakefile +1 -1
- data/i18n-docs.gemspec +9 -4
- data/lib/i18n-docs.rb +1 -2
- data/lib/i18n_docs/csv_to_yaml.rb +7 -12
- data/lib/i18n_docs/missing_keys_finder.rb +12 -15
- data/lib/i18n_docs/translation_file_export.rb +3 -11
- data/lib/i18n_docs/translations.rb +8 -6
- data/lib/i18n_docs/version.rb +1 -1
- data/lib/tasks/store_translations.rake +15 -19
- data/test/test_helper.rb +6 -4
- data/test/unit/csv_to_yaml_test.rb +30 -22
- data/test/unit/translation_file_export_test.rb +14 -18
- data/test/unit/translations_test.rb +8 -7
- metadata +34 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 856b480dbfe843f57e65141e6c217c80ee30944c95131d82eec949e700b19517
|
4
|
+
data.tar.gz: d572f840dda955f15e2f8794d517e5226f22527779d68a7b3b83a3133faa3c05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 218816a28ac21a8063ebcc84230d88d50ea30ee599b1c2d4292bd58e583cee9448b7ac8a51817151f5f3b856ab55b07393ebaa2ef6cbd39dc468260a0b32c769
|
7
|
+
data.tar.gz: 361d4054384bc505de49f635fd1532c65a396702a883de438023ef83dbb72e5dc82740f82c772ea9871d34f908ab8c9f706df9fbc9b25ab1cf1fbc39299affc5
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby-version: [head, 3.0, 2.7, 2.6, 2.5, 2.4, 2.3]
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby-version }}
|
19
|
+
bundler-cache: true
|
20
|
+
- name: Run tests
|
21
|
+
run: bundle exec rake test
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2017-07-11 14:36:42 +0200 using RuboCop version 0.48.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 8
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 30
|
12
|
+
|
13
|
+
# Offense count: 1
|
14
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
15
|
+
Metrics/BlockLength:
|
16
|
+
Max: 37
|
17
|
+
|
18
|
+
# Offense count: 4
|
19
|
+
# Configuration parameters: CountComments.
|
20
|
+
Metrics/MethodLength:
|
21
|
+
Max: 26
|
22
|
+
|
23
|
+
# Offense count: 1
|
24
|
+
Metrics/PerceivedComplexity:
|
25
|
+
Max: 8
|
26
|
+
|
27
|
+
# Offense count: 1
|
28
|
+
Style/AsciiComments:
|
29
|
+
Exclude:
|
30
|
+
- 'lib/i18n_docs/missing_keys_finder.rb'
|
31
|
+
|
32
|
+
# Offense count: 1
|
33
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
34
|
+
# SupportedStyles: nested, compact
|
35
|
+
Style/ClassAndModuleChildren:
|
36
|
+
Exclude:
|
37
|
+
- 'lib/i18n-docs.rb'
|
38
|
+
|
39
|
+
# Offense count: 1
|
40
|
+
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
|
41
|
+
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
|
42
|
+
Style/FileName:
|
43
|
+
Exclude:
|
44
|
+
- 'lib/i18n-docs.rb'
|
45
|
+
|
46
|
+
# Offense count: 1
|
47
|
+
# Configuration parameters: MinBodyLength.
|
48
|
+
Style/GuardClause:
|
49
|
+
Exclude:
|
50
|
+
- 'lib/i18n_docs/csv_to_yaml.rb'
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
# i18n-docs
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/renuo/i18n-docs.svg?branch=master)](https://travis-ci.org/renuo/i18n-docs)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/renuo/i18n-docs/badges/gpa.svg)](https://codeclimate.com/github/renuo/i18n-docs)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/renuo/i18n-docs/badges/coverage.svg)](https://codeclimate.com/github/renuo/i18n-docs/coverage)
|
6
|
+
[![Issue Count](https://codeclimate.com/github/renuo/i18n-docs/badges/issue_count.svg)](https://codeclimate.com/github/renuo/i18n-docs)
|
7
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/renuo/i18n-docs.svg)](https://gemnasium.com/github.com/renuo/i18n-docs)
|
8
|
+
|
3
9
|
**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.
|
4
10
|
|
5
11
|
Although we use it with Google Docs, it could be used with any CSV file.
|
6
12
|
|
7
13
|
*this gem is currently in use and tested with Rails 3.1. It probably works with other 3.x versions, but probably not 2.x at the moment.*
|
8
14
|
|
9
|
-
## Features
|
15
|
+
## Features
|
10
16
|
|
11
17
|
* download translations from multiple Google spreadsheets and store to YAML files
|
12
18
|
* import/export YAML files with a Google Spreadsheet
|
13
19
|
* check YAML files for missing translations (`rake i18n:find_missing_keys`)
|
14
20
|
|
21
|
+
## Tests
|
22
|
+
|
23
|
+
Verify the quality of this gem by checking out this repo and running:
|
24
|
+
|
25
|
+
bundle install
|
26
|
+
rake test
|
27
|
+
|
15
28
|
## Usage
|
16
29
|
|
17
30
|
### Configuration
|
@@ -99,6 +112,19 @@ Following Rake tasks are added by the GEM to your Rails project:
|
|
99
112
|
![screenshot](http://dl.dropbox.com/u/385855/Screenshots/oom_.png)
|
100
113
|
* 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
|
101
114
|
|
115
|
+
|
116
|
+
## Configuraiton
|
117
|
+
|
118
|
+
You can include a set of substitions for preprocessing the CSV after downloading it and before parsing it. Here is an example that replaces non-breaking spaces with normal ones.
|
119
|
+
|
120
|
+
files:
|
121
|
+
navigation.yml: "https://docs.google.com/spreadsheet/pub?key=ab43...34f3&single=true&gid=0&output=csv"
|
122
|
+
|
123
|
+
substitutions:
|
124
|
+
- from: "\u00A0"
|
125
|
+
to: ' '
|
126
|
+
|
127
|
+
|
102
128
|
## Error Handling
|
103
129
|
|
104
130
|
I case of the error `OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed` when running `rake i18n:import_translations`
|
@@ -115,6 +141,15 @@ Follow this: http://stackoverflow.com/questions/12562697/opensslsslsslerror-ssl-
|
|
115
141
|
|
116
142
|
## CHANGELOG
|
117
143
|
|
144
|
+
### 0.1.1
|
145
|
+
|
146
|
+
* add `substitutions` config
|
147
|
+
|
148
|
+
|
149
|
+
### 0.1.0
|
150
|
+
|
151
|
+
* usable outside of rails
|
152
|
+
|
118
153
|
### 0.0.10
|
119
154
|
|
120
155
|
* rename module to be inline with the gem-name
|
data/Rakefile
CHANGED
data/i18n-docs.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
2
|
# Maintain your gem's version:
|
3
3
|
require 'i18n_docs/version'
|
4
4
|
|
@@ -7,9 +7,12 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.name = 'i18n-docs'
|
8
8
|
s.version = I18nDocs::VERSION
|
9
9
|
s.date = '2013-05-10'
|
10
|
-
s.summary =
|
11
|
-
s.description =
|
12
|
-
|
10
|
+
s.summary = 'Maintain translations in Google Docs and export them to your Rails project.'
|
11
|
+
s.description = 'GEM providing helper scripts to manage i18n translations in Google Docs. '\
|
12
|
+
'Features: check YAML files for missing translations; export YAML files to CSV; '\
|
13
|
+
'download translations from multiple Google spreadsheets and store to YAML files'
|
14
|
+
s.authors = ['Georg Kunz', 'Ivan Jovanovic', 'Jeremy Seitz', 'Eduard Schäli', 'Robin Wunderlin',
|
15
|
+
'Esteban Pastorino', 'Krzysztof Sakwerda']
|
13
16
|
s.email = 'eduard.schaeli@localsearch.ch'
|
14
17
|
s.files = `git ls-files`.split("\n")
|
15
18
|
s.homepage = 'https://github.com/local-ch/i18n-docs'
|
@@ -19,4 +22,6 @@ Gem::Specification.new do |s|
|
|
19
22
|
|
20
23
|
s.add_development_dependency('mocha', '~> 0.13.3')
|
21
24
|
s.add_development_dependency('test-unit', '~> 3.1.7')
|
25
|
+
s.add_development_dependency('simplecov', '~> 0.16.1')
|
26
|
+
s.add_development_dependency('rubocop', '~> 0.48.0')
|
22
27
|
end
|
data/lib/i18n-docs.rb
CHANGED
@@ -9,8 +9,7 @@ require 'i18n_docs/translation_file_export'
|
|
9
9
|
if defined?(Rails)
|
10
10
|
class I18nDocs::Railtie < Rails::Railtie
|
11
11
|
rake_tasks do
|
12
|
-
Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
|
12
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |f| load f }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module I18nDocs
|
2
|
-
|
3
2
|
class CsvToYaml
|
4
|
-
|
5
3
|
attr_reader :input_file, :output_file, :locales, :translations
|
6
4
|
|
7
5
|
def self.root_path
|
@@ -20,21 +18,19 @@ module I18nDocs
|
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
21
|
def write_files
|
25
22
|
@locales.each do |locale|
|
26
23
|
output_file_path = self.class.root_path.join('config', 'locales', locale, @output_file)
|
27
24
|
FileUtils.mkdir_p File.dirname(output_file_path)
|
28
25
|
|
29
26
|
File.open(output_file_path, 'w') do |file|
|
30
|
-
final_translation_hash = {locale => @translations[locale]}
|
31
|
-
file.puts YAML
|
27
|
+
final_translation_hash = { locale => @translations[locale] }
|
28
|
+
file.puts YAML.dump(final_translation_hash)
|
32
29
|
end
|
33
30
|
puts "File '#{@output_file}' for language '#{locale}' written to disc (#{output_file_path})"
|
34
31
|
end
|
35
32
|
end
|
36
33
|
|
37
|
-
|
38
34
|
def process
|
39
35
|
CSV.foreach(@input_file, headers: true) do |row|
|
40
36
|
process_row(row.to_hash)
|
@@ -47,14 +43,15 @@ module I18nDocs
|
|
47
43
|
|
48
44
|
key_elements = key.split('.')
|
49
45
|
@locales.each do |locale|
|
50
|
-
|
46
|
+
unless row_hash.key?(locale)
|
47
|
+
raise "Locale missing for key #{key}! (locales in app: #{@locales} / locales in file: #{row_hash.keys})"
|
48
|
+
end
|
51
49
|
store_translation(key_elements, locale, row_hash[locale])
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
|
56
53
|
def store_translation(keys, locale, value)
|
57
|
-
return nil if value.nil?
|
54
|
+
return nil if value.nil? # we don't store keys that don't have a valid value
|
58
55
|
# Google Spreadsheet does not export empty strings and therefore we use '_' as a replacement char.
|
59
56
|
value = '' if value == '_'
|
60
57
|
|
@@ -63,7 +60,7 @@ module I18nDocs
|
|
63
60
|
leaf = keys.last
|
64
61
|
data_hash = tree.inject(@translations[locale]) do |memo, k|
|
65
62
|
if memo.is_a? Hash
|
66
|
-
if memo.
|
63
|
+
if memo.key?(k)
|
67
64
|
memo[k]
|
68
65
|
else
|
69
66
|
memo[k] = {}
|
@@ -79,7 +76,5 @@ module I18nDocs
|
|
79
76
|
|
80
77
|
data_hash[leaf] = value
|
81
78
|
end
|
82
|
-
|
83
79
|
end
|
84
|
-
|
85
80
|
end
|
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
1
|
module I18nDocs
|
3
2
|
class MissingKeysFinder
|
4
3
|
def initialize(backend)
|
5
4
|
@backend = backend
|
6
|
-
|
7
|
-
|
5
|
+
load_config
|
6
|
+
load_translations
|
8
7
|
end
|
9
8
|
|
10
9
|
# Returns an array with all keys from all locales
|
11
10
|
def all_keys
|
12
|
-
I18n.backend.send(:translations).collect do |
|
11
|
+
I18n.backend.send(:translations).collect do |_check_locale, translations|
|
13
12
|
collect_keys([], translations).sort
|
14
13
|
end.flatten.uniq
|
15
14
|
end
|
@@ -20,12 +19,10 @@ module I18nDocs
|
|
20
19
|
|
21
20
|
missing_keys = {}
|
22
21
|
all_keys.each do |key|
|
23
|
-
|
24
22
|
I18n.available_locales.each do |locale|
|
25
|
-
|
26
23
|
skip = false
|
27
24
|
ls = locale.to_s
|
28
|
-
|
25
|
+
unless @yaml[ls].nil?
|
29
26
|
@yaml[ls].each do |re|
|
30
27
|
if key.match(re)
|
31
28
|
skip = true
|
@@ -45,24 +42,26 @@ module I18nDocs
|
|
45
42
|
end
|
46
43
|
|
47
44
|
output_missing_keys(missing_keys)
|
48
|
-
|
45
|
+
missing_keys
|
49
46
|
end
|
50
47
|
|
48
|
+
# rubocop:disable Metrics/LineLength
|
51
49
|
def output_available_locales
|
52
50
|
puts "#{I18n.available_locales.size} #{I18n.available_locales.size == 1 ? 'locale' : 'locales'} available: #{I18n.available_locales.join(', ')}"
|
53
51
|
end
|
54
52
|
|
55
53
|
def output_missing_keys(missing_keys)
|
56
|
-
if missing_keys.
|
54
|
+
if missing_keys.any?
|
57
55
|
puts "#{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales:"
|
58
56
|
missing_keys.keys.sort.each do |key|
|
59
57
|
puts "'#{key}': Missing from #{missing_keys[key].collect(&:inspect).join(', ')}"
|
60
58
|
end
|
61
59
|
puts "\nERROR: #{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales."
|
62
60
|
else
|
63
|
-
puts
|
61
|
+
puts 'No keys are missing'
|
64
62
|
end
|
65
63
|
end
|
64
|
+
# rubocop:enable Metrics/LineLength
|
66
65
|
|
67
66
|
def output_unique_key_stats(keys)
|
68
67
|
number_of_keys = keys.size
|
@@ -79,13 +78,13 @@ module I18nDocs
|
|
79
78
|
full_keys << new_scope.join('.')
|
80
79
|
end
|
81
80
|
end
|
82
|
-
|
81
|
+
full_keys
|
83
82
|
end
|
84
83
|
|
85
84
|
# Returns true if key exists in the given locale
|
86
85
|
def key_exists?(key, locale)
|
87
86
|
I18n.locale = locale
|
88
|
-
I18n.translate(key, :
|
87
|
+
I18n.translate(key, raise: true)
|
89
88
|
return true
|
90
89
|
rescue I18n::MissingInterpolationArgument
|
91
90
|
return true
|
@@ -103,10 +102,8 @@ module I18nDocs
|
|
103
102
|
begin
|
104
103
|
@yaml = YAML.load_file(File.join(Rails.root, 'config', 'ignore_missing_i18n_keys.yml'))
|
105
104
|
rescue
|
106
|
-
STDERR.puts
|
105
|
+
STDERR.puts 'No ignore_missing_keys.yml config file.'
|
107
106
|
end
|
108
|
-
|
109
107
|
end
|
110
|
-
|
111
108
|
end
|
112
109
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module I18nDocs
|
2
2
|
class TranslationFileExport
|
3
|
-
|
4
3
|
attr_accessor :translations
|
5
4
|
|
6
5
|
def initialize(source_dir, source_file, output_dir, locales)
|
@@ -8,25 +7,23 @@ module I18nDocs
|
|
8
7
|
@source_file = source_file
|
9
8
|
|
10
9
|
@output_file = File.join(output_dir, source_file.gsub('.yml', '.csv'))
|
11
|
-
@locales = locales.map
|
10
|
+
@locales = locales.map(&:to_s)
|
12
11
|
|
13
12
|
@translations = {}
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
15
|
def export
|
18
16
|
load_translations
|
19
17
|
write_to_csv
|
20
18
|
end
|
21
19
|
|
22
|
-
|
23
20
|
def write_to_csv
|
24
21
|
main_locale = @locales.include?('en') ? 'en' : @locales.first
|
25
22
|
|
26
23
|
puts " #{@source_file}: write CSV to '#{@output_file}' \n\n"
|
27
24
|
|
28
|
-
CSV.open(@output_file,
|
29
|
-
csv << ([
|
25
|
+
CSV.open(@output_file, 'wb') do |csv|
|
26
|
+
csv << (['key'] + @locales)
|
30
27
|
|
31
28
|
@translations[main_locale].keys.each do |key|
|
32
29
|
values = @locales.map do |locale|
|
@@ -35,10 +32,8 @@ module I18nDocs
|
|
35
32
|
csv << values.unshift(key)
|
36
33
|
end
|
37
34
|
end
|
38
|
-
|
39
35
|
end
|
40
36
|
|
41
|
-
|
42
37
|
def load_translations
|
43
38
|
@locales.each do |locale|
|
44
39
|
translation_hash = load_language(locale)
|
@@ -47,7 +42,6 @@ module I18nDocs
|
|
47
42
|
end
|
48
43
|
|
49
44
|
def load_language(locale)
|
50
|
-
|
51
45
|
puts " #{@source_file}: load translations for '#{locale}'"
|
52
46
|
|
53
47
|
input_file = File.join(@source_dir, locale, @source_file)
|
@@ -71,7 +65,5 @@ module I18nDocs
|
|
71
65
|
end
|
72
66
|
flat_hash
|
73
67
|
end
|
74
|
-
|
75
68
|
end
|
76
|
-
|
77
69
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# Order of method calls
|
3
2
|
# download_files
|
4
3
|
# store_translations
|
@@ -6,7 +5,6 @@
|
|
6
5
|
#
|
7
6
|
module I18nDocs
|
8
7
|
class Translations
|
9
|
-
|
10
8
|
attr_accessor :locales, :tmp_folder, :config_file, :csv_files
|
11
9
|
|
12
10
|
def initialize(config_file = nil, tmp_folder = nil)
|
@@ -32,8 +30,8 @@ module I18nDocs
|
|
32
30
|
def download_files
|
33
31
|
files = @settings['files']
|
34
32
|
files.each do |target_file, url|
|
35
|
-
#ensure .yml filename
|
36
|
-
target_file
|
33
|
+
# ensure .yml filename
|
34
|
+
target_file += '.yml' if target_file !~ /\.yml$/
|
37
35
|
# download file to tmp directory
|
38
36
|
tmp_file = File.basename(target_file).gsub('.yml', '.csv')
|
39
37
|
tmp_file = File.join(@tmp_folder, tmp_file)
|
@@ -54,7 +52,7 @@ module I18nDocs
|
|
54
52
|
|
55
53
|
def clean_up
|
56
54
|
# remove all tmp files
|
57
|
-
@csv_files.each do |
|
55
|
+
@csv_files.each do |_target_file, csv_file|
|
58
56
|
File.unlink(csv_file)
|
59
57
|
end
|
60
58
|
end
|
@@ -62,10 +60,14 @@ module I18nDocs
|
|
62
60
|
def download(url, destination_file)
|
63
61
|
puts "Download '#{url}' to '#{destination_file}'"
|
64
62
|
doc_data = open(url).read.force_encoding('UTF-8')
|
63
|
+
if (subs = @settings['substitutions'])
|
64
|
+
subs.each do |sub|
|
65
|
+
doc_data.gsub! sub['from'], sub['to']
|
66
|
+
end
|
67
|
+
end
|
65
68
|
File.open(destination_file, 'w') do |dst|
|
66
69
|
dst.write(doc_data)
|
67
70
|
end
|
68
71
|
end
|
69
|
-
|
70
72
|
end
|
71
73
|
end
|
data/lib/i18n_docs/version.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
|
2
2
|
namespace :i18n do
|
3
|
-
|
4
|
-
|
5
|
-
task :missing_keys => :environment do
|
3
|
+
desc 'Find and list translation keys that do not exist in all locales'
|
4
|
+
task missing_keys: :environment do
|
6
5
|
finder = I18nDocs::MissingKeysFinder.new(I18n.backend)
|
7
6
|
finder.find_missing_keys
|
8
7
|
end
|
9
8
|
|
10
|
-
desc
|
11
|
-
task :
|
12
|
-
|
9
|
+
desc 'Download translations from Google Spreadsheet and save them to YAML files.'
|
10
|
+
task import_translations: :environment do
|
13
11
|
config_file = I18nDocs::CsvToYaml.root_path.join('config', 'translations.yml')
|
14
|
-
raise "No config file 'config/translations.yml' found."
|
12
|
+
raise "No config file 'config/translations.yml' found." unless File.exist?(config_file)
|
15
13
|
|
16
14
|
tmp_dir = I18nDocs::CsvToYaml.root_path.join('tmp')
|
17
15
|
Dir.mkdir(tmp_dir) unless Dir.exist?(tmp_dir)
|
@@ -20,24 +18,23 @@ namespace :i18n do
|
|
20
18
|
translations.download_files
|
21
19
|
translations.store_translations
|
22
20
|
translations.clean_up
|
23
|
-
|
24
21
|
end
|
25
22
|
|
26
|
-
desc
|
27
|
-
task :
|
23
|
+
desc 'Export all language files to CSV files (only files contained in en folder are considered)'
|
24
|
+
task export_translations: :environment do
|
28
25
|
source_dir = I18nDocs::CsvToYaml.root_path.join('config', 'locales')
|
29
26
|
output_dir = I18nDocs::CsvToYaml.root_path.join('tmp')
|
30
27
|
locales = I18n.available_locales
|
31
28
|
|
32
29
|
input_files = Dir[File.join(source_dir, ENV['locale'] || 'en', '*.yml')]
|
33
30
|
|
34
|
-
puts
|
31
|
+
puts ''
|
35
32
|
puts " Detected locales: #{locales}"
|
36
|
-
puts
|
37
|
-
input_files.each {|f| puts " * #{File.basename(f)}" }
|
33
|
+
puts ' Detected files:'
|
34
|
+
input_files.each { |f| puts " * #{File.basename(f)}" }
|
38
35
|
|
39
|
-
puts
|
40
|
-
puts
|
36
|
+
puts ''
|
37
|
+
puts ' Start exporting files:'
|
41
38
|
|
42
39
|
input_files.each do |file|
|
43
40
|
file = File.basename(file)
|
@@ -45,9 +42,8 @@ namespace :i18n do
|
|
45
42
|
exporter.export
|
46
43
|
end
|
47
44
|
|
48
|
-
puts
|
49
|
-
puts
|
50
|
-
puts
|
45
|
+
puts ''
|
46
|
+
puts ' CSV files can be removed safely after uploading them manually to Google Spreadsheet.'
|
47
|
+
puts ''
|
51
48
|
end
|
52
|
-
|
53
49
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
1
4
|
require 'test/unit'
|
2
5
|
require 'fileutils'
|
3
6
|
require 'mocha/setup'
|
7
|
+
|
4
8
|
require 'i18n-docs'
|
5
9
|
|
6
10
|
Rails = Struct.new(:dummy)
|
7
11
|
|
8
12
|
module TestHelper
|
9
|
-
|
10
13
|
def fixture_path
|
11
14
|
File.join(File.expand_path(File.dirname(__FILE__)), 'fixtures')
|
12
15
|
end
|
@@ -16,11 +19,10 @@ module TestHelper
|
|
16
19
|
end
|
17
20
|
|
18
21
|
def create_tmp_dir
|
19
|
-
FileUtils
|
22
|
+
FileUtils.mkdir(tmp_dir) unless File.exist?(tmp_dir)
|
20
23
|
end
|
21
24
|
|
22
25
|
def remove_tmp_dir
|
23
|
-
FileUtils
|
26
|
+
FileUtils.rmtree(tmp_dir)
|
24
27
|
end
|
25
|
-
|
26
28
|
end
|
@@ -3,7 +3,6 @@ require 'test_helper'
|
|
3
3
|
# run test: ruby -I test/ -I lib/ test/unit/csv_to_yaml_test.rb
|
4
4
|
|
5
5
|
module UnitTests
|
6
|
-
|
7
6
|
class CsvToYamlTest < Test::Unit::TestCase
|
8
7
|
include TestHelper
|
9
8
|
|
@@ -12,9 +11,9 @@ module UnitTests
|
|
12
11
|
|
13
12
|
@input_file = File.join(fixture_path, 'minimal.csv')
|
14
13
|
@output_file = File.join(tmp_dir, 'test.yml')
|
15
|
-
@locales = [
|
14
|
+
@locales = %w[de en]
|
16
15
|
|
17
|
-
Rails.stubs(:root).returns(stub(:
|
16
|
+
Rails.stubs(:root).returns(stub(join: @output_file))
|
18
17
|
|
19
18
|
@csv_to_yaml = I18nDocs::CsvToYaml.new(@input_file, @output_file, @locales)
|
20
19
|
end
|
@@ -24,8 +23,8 @@ module UnitTests
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def test_process_row
|
27
|
-
row1 = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => 'Telefonbuch der Schweiz'}
|
28
|
-
row2 = {'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen'}
|
26
|
+
row1 = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => 'Telefonbuch der Schweiz' }
|
27
|
+
row2 = { 'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen' }
|
29
28
|
@csv_to_yaml.process_row(row1)
|
30
29
|
@csv_to_yaml.process_row(row2)
|
31
30
|
|
@@ -37,8 +36,8 @@ module UnitTests
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def test_empty_row
|
40
|
-
row1 = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => 'Telefonbuch der Schweiz'}
|
41
|
-
row2 = {'key' => nil, 'en' => 'Welcome', 'de' => 'Willkommen'}
|
39
|
+
row1 = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => 'Telefonbuch der Schweiz' }
|
40
|
+
row2 = { 'key' => nil, 'en' => 'Welcome', 'de' => 'Willkommen' }
|
42
41
|
@csv_to_yaml.process_row(row1)
|
43
42
|
@csv_to_yaml.process_row(row2)
|
44
43
|
|
@@ -48,18 +47,18 @@ module UnitTests
|
|
48
47
|
end
|
49
48
|
|
50
49
|
def test_row_containing_non_locale_columns
|
51
|
-
row = {'key' => 'homepage.title', 'en' =>
|
50
|
+
row = { 'key' => 'homepage.title', 'en' => 'We are the Phonebook', 'de' => 'Test DE',
|
51
|
+
'comment' => 'Test comment' }
|
52
52
|
@csv_to_yaml.process_row(row)
|
53
53
|
|
54
54
|
translations = @csv_to_yaml.translations
|
55
55
|
assert_equal 'We are the Phonebook', translations['en']['homepage']['title']
|
56
56
|
end
|
57
57
|
|
58
|
-
|
59
58
|
def test_empty_string_replacement_value
|
60
59
|
# As Google Spreadsheet does not export empty cells we use '_' as a fake whitespace which
|
61
60
|
# we replace with an empty string during CVS2YAML conversion.
|
62
|
-
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => '_'}
|
61
|
+
row = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => '_' }
|
63
62
|
@csv_to_yaml.process_row(row)
|
64
63
|
|
65
64
|
translations = @csv_to_yaml.translations
|
@@ -67,9 +66,8 @@ module UnitTests
|
|
67
66
|
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
68
67
|
end
|
69
68
|
|
70
|
-
|
71
69
|
def test_empty_string_value
|
72
|
-
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => ''}
|
70
|
+
row = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => '' }
|
73
71
|
@csv_to_yaml.process_row(row)
|
74
72
|
|
75
73
|
translations = @csv_to_yaml.translations
|
@@ -77,9 +75,8 @@ module UnitTests
|
|
77
75
|
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
78
76
|
end
|
79
77
|
|
80
|
-
|
81
78
|
def test_space_value
|
82
|
-
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => ' '}
|
79
|
+
row = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => ' ' }
|
83
80
|
@csv_to_yaml.process_row(row)
|
84
81
|
|
85
82
|
translations = @csv_to_yaml.translations
|
@@ -88,7 +85,7 @@ module UnitTests
|
|
88
85
|
end
|
89
86
|
|
90
87
|
def test_nil_value
|
91
|
-
row = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil}
|
88
|
+
row = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil }
|
92
89
|
@csv_to_yaml.process_row(row)
|
93
90
|
|
94
91
|
translations = @csv_to_yaml.translations
|
@@ -96,10 +93,9 @@ module UnitTests
|
|
96
93
|
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
97
94
|
end
|
98
95
|
|
99
|
-
|
100
96
|
def test_nil_value_deep_structure
|
101
|
-
row1 = {'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil}
|
102
|
-
row2 = {'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen'}
|
97
|
+
row1 = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland', 'de' => nil }
|
98
|
+
row2 = { 'key' => 'homepage.welcome', 'en' => 'Welcome', 'de' => 'Willkommen' }
|
103
99
|
@csv_to_yaml.process_row(row1)
|
104
100
|
@csv_to_yaml.process_row(row2)
|
105
101
|
|
@@ -109,15 +105,28 @@ module UnitTests
|
|
109
105
|
assert_equal 'Phonebook of Switzerland', translations['en']['homepage']['meta']['title']
|
110
106
|
end
|
111
107
|
|
108
|
+
def test_row_for_missing_locale_key
|
109
|
+
row = { 'key' => 'homepage.meta.title', 'en' => 'Phonebook of Switzerland' }
|
110
|
+
assert_raise RuntimeError.new('Locale missing for key homepage.meta.title! (locales in app: ["de", "en"] / locales in file: ["en"])') do
|
111
|
+
@csv_to_yaml.process_row(row)
|
112
|
+
end
|
113
|
+
end
|
112
114
|
|
113
115
|
def test_store_translations
|
114
|
-
keys = [
|
116
|
+
keys = %w[homepage meta title]
|
115
117
|
@csv_to_yaml.store_translation(keys, 'de', 'Telefonbuch der Schweiz')
|
116
118
|
|
117
119
|
translations = @csv_to_yaml.translations
|
118
120
|
assert_equal 'Telefonbuch der Schweiz', translations['de']['homepage']['meta']['title']
|
119
121
|
end
|
120
122
|
|
123
|
+
def test_store_translations_with_faulty_locale
|
124
|
+
keys = %w[homepage meta title]
|
125
|
+
assert_raise RuntimeError.new("Error around key 'homepage.meta.title': Expected nil to be a Hash") do
|
126
|
+
@csv_to_yaml.store_translation(keys, 'zz', 'Telefonbuch der Schweiz')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
121
130
|
def test_process
|
122
131
|
@locales.each do |locale|
|
123
132
|
assert_empty @csv_to_yaml.translations[locale], "expected translation hash for locale '#{locale}' to be empty"
|
@@ -141,7 +150,7 @@ module UnitTests
|
|
141
150
|
end
|
142
151
|
|
143
152
|
def test_key_has_spaces
|
144
|
-
row = {'key' => 'has. space', 'en' => 'yes', 'de' => 'ja'}
|
153
|
+
row = { 'key' => 'has. space', 'en' => 'yes', 'de' => 'ja' }
|
145
154
|
@csv_to_yaml.process_row(row)
|
146
155
|
|
147
156
|
translations = @csv_to_yaml.translations
|
@@ -153,10 +162,9 @@ module UnitTests
|
|
153
162
|
@input_file = File.join(fixture_path, 'error.csv')
|
154
163
|
@csv_to_yaml = I18nDocs::CsvToYaml.new(@input_file, @output_file, @locales)
|
155
164
|
|
156
|
-
assert_raise "Error around key 'top_level.key.another_key': Expected \"Value2\" to be a Hash" do
|
165
|
+
assert_raise RuntimeError.new("Error around key 'top_level.key.another_key': Expected \"Value2\" to be a Hash") do
|
157
166
|
@csv_to_yaml.process
|
158
167
|
end
|
159
168
|
end
|
160
|
-
|
161
169
|
end
|
162
170
|
end
|
@@ -12,7 +12,7 @@ module UnitTests
|
|
12
12
|
source_dir = fixture_path
|
13
13
|
source_file = 'header.yml'
|
14
14
|
output_dir = tmp_dir
|
15
|
-
locales = [
|
15
|
+
locales = %w[en de]
|
16
16
|
|
17
17
|
@exporter = I18nDocs::TranslationFileExport.new(source_dir, source_file, output_dir, locales)
|
18
18
|
@output_file = File.join(output_dir, 'header.csv')
|
@@ -22,30 +22,28 @@ module UnitTests
|
|
22
22
|
remove_tmp_dir
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
25
|
def test_export
|
27
26
|
assert !File.exist?(@output_file)
|
28
27
|
@exporter.export
|
29
|
-
assert File.exist?(@output_file),
|
28
|
+
assert File.exist?(@output_file), 'Expected to have a CSV file written'
|
30
29
|
end
|
31
30
|
|
32
31
|
def dtest_load_language
|
33
32
|
translations = @exporter.load_language('de')
|
34
33
|
|
35
|
-
assert translations,
|
36
|
-
assert_equal translations['header']['search'],
|
34
|
+
assert translations, 'Expected to return a hash with translations'
|
35
|
+
assert_equal translations['header']['search'], 'Finden'
|
37
36
|
end
|
38
37
|
|
39
38
|
def dtest_flatten_translations_hash
|
40
|
-
translation_hash = {'a' => {
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
}
|
39
|
+
translation_hash = { 'a' => {
|
40
|
+
'I' => '1',
|
41
|
+
'II' => '2',
|
42
|
+
'III' => {
|
43
|
+
'Z' => '3'
|
44
|
+
}
|
45
|
+
},
|
46
|
+
'b' => '4' }
|
49
47
|
|
50
48
|
flat = @exporter.flatten_translations_hash(translation_hash, [])
|
51
49
|
assert_equal '1', flat['a.I']
|
@@ -66,8 +64,8 @@ module UnitTests
|
|
66
64
|
end
|
67
65
|
|
68
66
|
def dtest_write_to_csv
|
69
|
-
@exporter.translations = {'de' => {'numbers.one' => 'eins'},
|
70
|
-
|
67
|
+
@exporter.translations = { 'de' => { 'numbers.one' => 'eins' },
|
68
|
+
'en' => { 'numbers.one' => 'one' } }
|
71
69
|
|
72
70
|
@exporter.write_to_csv
|
73
71
|
|
@@ -75,7 +73,5 @@ module UnitTests
|
|
75
73
|
assert_match(/^key,(en|de|,){3}$/, output)
|
76
74
|
assert_match(/^numbers.one,(one|eins|,){3}$/, output)
|
77
75
|
end
|
78
|
-
|
79
|
-
|
80
76
|
end
|
81
77
|
end
|
@@ -12,13 +12,13 @@ module UnitTests
|
|
12
12
|
config_file = File.join(fixture_path, 'config.yml')
|
13
13
|
@translations = I18nDocs::Translations.new(config_file, tmp_dir)
|
14
14
|
@translations.tmp_folder = tmp_dir
|
15
|
-
@translations.locales = [
|
15
|
+
@translations.locales = %w[de en]
|
16
16
|
|
17
17
|
@fixture_file = File.join(fixture_path, 'minimal.csv')
|
18
18
|
@tmp_file = File.join(tmp_dir, 'downloaded.csv')
|
19
19
|
@output_file = File.join(tmp_dir, 'test.yml')
|
20
20
|
|
21
|
-
Rails.stubs(:root).returns(stub(:
|
21
|
+
Rails.stubs(:root).returns(stub(join: @output_file))
|
22
22
|
end
|
23
23
|
|
24
24
|
def teardown
|
@@ -27,22 +27,23 @@ module UnitTests
|
|
27
27
|
|
28
28
|
def test_download
|
29
29
|
assert !File.exist?(@tmp_file)
|
30
|
-
|
30
|
+
sheet_url = 'https://docs.google.com/spreadsheets/d/1PbmkqamXuNyP7gnVARpeCfV8rA7WvX98dTqsQB3Wdts/pub?output=csv'
|
31
|
+
@translations.download(sheet_url, @tmp_file)
|
31
32
|
assert File.exist?(@tmp_file), "Expected to have downloaded Google Spreadsheet to '#{@tmp_file}'"
|
32
33
|
end
|
33
34
|
|
34
35
|
def test_cleanup
|
35
|
-
@translations.csv_files = {'dummy.yml' => @tmp_file}
|
36
|
-
File.open(@tmp_file,
|
36
|
+
@translations.csv_files = { 'dummy.yml' => @tmp_file }
|
37
|
+
File.open(@tmp_file, 'w') {}
|
37
38
|
assert File.exist?(@tmp_file)
|
38
39
|
@translations.clean_up
|
39
|
-
assert !File.exist?(@tmp_file),
|
40
|
+
assert !File.exist?(@tmp_file), 'Expected to delete file'
|
40
41
|
end
|
41
42
|
|
42
43
|
def test_store_translations
|
43
44
|
assert !File.exist?(@output_file)
|
44
45
|
|
45
|
-
@translations.csv_files = {@output_file => @fixture_file}
|
46
|
+
@translations.csv_files = { @output_file => @fixture_file }
|
46
47
|
@translations.store_translations
|
47
48
|
|
48
49
|
assert File.exist?(@output_file)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: i18n-docs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Georg Kunz
|
@@ -58,6 +58,34 @@ dependencies:
|
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 3.1.7
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: simplecov
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.16.1
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 0.16.1
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rubocop
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.48.0
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.48.0
|
61
89
|
description: 'GEM providing helper scripts to manage i18n translations in Google Docs.
|
62
90
|
Features: check YAML files for missing translations; export YAML files to CSV; download
|
63
91
|
translations from multiple Google spreadsheets and store to YAML files'
|
@@ -66,7 +94,11 @@ executables: []
|
|
66
94
|
extensions: []
|
67
95
|
extra_rdoc_files: []
|
68
96
|
files:
|
97
|
+
- ".codeclimate.yml"
|
98
|
+
- ".github/workflows/ci.yml"
|
69
99
|
- ".gitignore"
|
100
|
+
- ".rubocop.yml"
|
101
|
+
- ".rubocop_todo.yml"
|
70
102
|
- Gemfile
|
71
103
|
- LICENSE.md
|
72
104
|
- README.md
|
@@ -107,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
139
|
- !ruby/object:Gem::Version
|
108
140
|
version: '0'
|
109
141
|
requirements: []
|
110
|
-
|
111
|
-
rubygems_version: 2.6.5
|
142
|
+
rubygems_version: 3.1.6
|
112
143
|
signing_key:
|
113
144
|
specification_version: 4
|
114
145
|
summary: Maintain translations in Google Docs and export them to your Rails project.
|