csv2strings 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1,11 +1,27 @@
1
+ # Generated files
1
2
  *.csv
2
3
  *.strings
3
4
  *.lproj
4
- .rbenv-version
5
+ *.gem
5
6
  *~
6
7
  *#
7
- *.gem
8
- *.sublime-project
9
8
  #*#
9
+
10
+ # Config file
10
11
  .csvconverter
11
- coverage
12
+
13
+ # SimpleCov
14
+ coverage
15
+
16
+ # ignore Gemfile.lock as http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
17
+ # this should solve the fastercsv issue
18
+ Gemfile.lock
19
+
20
+ # Ruby package manager files
21
+ .ruby-version
22
+ .rbenv-version
23
+ .rvmrc
24
+
25
+ # Sublime Text
26
+ *.sublime-workspace
27
+ *.sublime-project
data/.travis.yml CHANGED
@@ -1,6 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 1.9.2
5
- - 1.8.7
6
- - 2.0.0
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 1.8.7
6
+ - 2.0.0
7
+ deploy:
8
+ provider: rubygems
9
+ api_key:
10
+ secure: Xjq+v+jEU6wK4BtyfnV1elegNcxK6Ah/O99Sn9c2IlkCmJ1wxLBouqzEiSorSJ4IOMa5H2y3gwo5GXOr6Y7d8huyGrPuBeCSGqAmH77wNCIv7G+jnLiYb1sRZbtKcPW2QaN6JF81qDIelwyspMfo6/ug1qN1x323UaxZl7f7nUE=
11
+ gem: csv2strings
12
+ on:
13
+ tags: true
14
+ repo: netbe/CSV-to-iOS-Localizable.strings-converter
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in teachmehowtomakearubygem.gemspec
4
- gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'coveralls', :require => false, :platforms => [:ruby_19, :ruby_20]
8
+ end
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  [![Build Status](https://secure.travis-ci.org/netbe/CSV-to-iOS-Localizable.strings-converter.png?branch=master)](http://travis-ci.org/netbe/CSV-to-iOS-Localizable.strings-converter)
2
2
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/netbe/CSV-to-iOS-Localizable.strings-converter)
3
+ [![Coverage Status](https://coveralls.io/repos/netbe/CSV-to-iOS-Localizable.strings-converter/badge.png)](https://coveralls.io/r/netbe/CSV-to-iOS-Localizable.strings-converter)
3
4
  # Introduction
4
5
  This script converts a csv file of translations into iOS .strings files and vice-versa.
5
6
 
@@ -33,10 +34,6 @@ Edge version can be found on `develop` branch.
33
34
 
34
35
  Run `bundle install` to install all the dependencies. Tests are done with `Test::Unit` so run `rake test` to run all the test suite.
35
36
 
36
- # Todo
37
+ # Todo & Known issues
37
38
 
38
- See GitHub isssues
39
-
40
- # Known issues
41
-
42
- None
39
+ See GitHub issues
data/Rakefile CHANGED
@@ -2,8 +2,10 @@ require 'rake/testtask'
2
2
 
3
3
  Rake::TestTask.new do |t|
4
4
  t.libs << "test"
5
- t.test_files = FileList['test/**/*_test.rb']
5
+ t.test_files = FileList['test/csvconverter/**/test_*.rb']
6
+ # t.warning = true
7
+ t.verbose = true
6
8
  end
7
9
 
8
10
  desc "Run tests"
9
- task :default => :test
11
+ task :default => :test
data/bin/csv2strings CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- require File.expand_path('../../lib/csv2strings_command', __FILE__)
3
-
2
+ $: << File.expand_path("../lib/", __FILE__)
3
+ require 'csvconverter/commands/csv2strings_command'
4
4
 
5
5
  CSV2StringsCommand.start
data/bin/strings2csv CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- require File.expand_path('../../lib/strings2csv_command', __FILE__)
3
-
2
+ $: << File.expand_path("../lib/", __FILE__)
3
+ require 'csvconverter/commands/strings2csv_command'
4
4
 
5
5
  Strings2CSVCommand.start
data/csv2strings.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'csv2strings'
3
- s.version = '0.2.1'
4
- s.date = '2013-10-15'
3
+ s.version = '0.2.2'
4
+ s.date = '2013-10-30'
5
5
  s.summary = "CSV to iOS Localizable.strings converter"
6
6
  s.description = "ruby script converts a CSV file of translations to Localizable.strings files and vice-versa"
7
7
  s.authors = ["François Benaiteau"]
@@ -14,8 +14,10 @@ Gem::Specification.new do |s|
14
14
 
15
15
  if RUBY_VERSION < '1.9'
16
16
  s.add_dependency "fastercsv"
17
+ s.add_dependency "nokogiri", "= 1.5.10"
18
+ s.add_dependency "orderedhash"
17
19
  end
18
- s.add_dependency "nokogiri", "= 1.5.10"
20
+
19
21
  s.add_dependency "google_drive", '0.3.6'
20
22
  s.add_development_dependency "rake"
21
23
 
data/lib/csvconverter.rb CHANGED
@@ -1,16 +1,21 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__)))
2
- require 'rubygems'
3
-
4
1
  CSVGEM = RUBY_VERSION.match(/^[0-1]\.[0-8]\./) ? 'faster_csv' : 'csv'
5
2
 
3
+ if RUBY_VERSION.match(/^[0-1]\.[0-8]\./)
4
+ require "orderedhash"
5
+ ORDERED_HASH_CLASS = OrderedHash
6
+ else
7
+ ORDERED_HASH_CLASS = Hash
8
+ end
9
+
6
10
  begin
7
11
  require CSVGEM
8
12
  rescue LoadError
9
13
  puts "Failed to load #{CSVGEM} (ruby #{RUBY_VERSION})"
10
14
  puts "gem install #{CSVGEM}"
11
- exit
15
+ abort
12
16
  end
13
17
 
14
18
  CSVParserClass = CSVGEM == 'csv' ? CSV : FasterCSV
15
- require "csv2strings/converter"
16
- require "strings2csv/converter"
19
+ require "csvconverter/csv2strings"
20
+ require "csvconverter/strings2csv"
21
+ require "csvconverter/google_doc"
@@ -1,8 +1,5 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__)))
2
1
  require 'yaml'
3
2
  require 'thor'
4
- require 'csvconverter'
5
- require 'google_doc'
6
3
 
7
4
  class Command < Thor
8
5
  include Thor::Actions
@@ -1,5 +1,4 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__)))
2
- require "command"
1
+ require "csvconverter/command"
3
2
  class CSV2StringsCommand < Command
4
3
  default_task :csv2strings
5
4
 
@@ -20,7 +19,7 @@ class CSV2StringsCommand < Command
20
19
  help("csv2strings")
21
20
  exit
22
21
  end
23
-
22
+
24
23
  filename ||= options['filename']
25
24
  if options['fetch']
26
25
  say "Downloading file from Google Drive"
@@ -33,12 +32,12 @@ class CSV2StringsCommand < Command
33
32
  help("csv2strings")
34
33
  exit
35
34
  end
36
-
35
+
37
36
  args = options.dup
38
37
  args.delete(:langs)
39
38
  args.delete(:filename)
40
- converter = CSV2Strings::Converter.new(filename, options[:langs], args)
41
- say converter.csv_to_dotstrings
39
+ converter = CSV2Strings.new(filename, options[:langs], args)
40
+ say converter.csv_to_dotstrings
42
41
  end
43
42
 
44
43
  end
@@ -1,6 +1,4 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__)))
2
- require "command"
3
-
1
+ require "csvconverter/command"
4
2
  class Strings2CSVCommand < Command
5
3
  default_task :strings2csv
6
4
 
@@ -15,10 +13,10 @@ class Strings2CSVCommand < Command
15
13
  unless options.has_key?('filenames')
16
14
  say "No value provided for required options '--filenames'"
17
15
  help("strings2csv")
18
- exit
16
+ return
19
17
  end
20
- converter = Strings2CSV::Converter.new(options)
18
+ converter = Strings2CSV.new(options)
21
19
  debug_values = converter.dotstrings_to_csv(!options[:dryrun])
22
- say debug_values.inspect if options[:dryrun]
20
+ say debug_values.inspect if options[:dryrun]
23
21
  end
24
- end
22
+ end
@@ -0,0 +1,132 @@
1
+ class CSV2Strings
2
+ attr_accessor :csv_filename, :output_file
3
+ attr_accessor :langs, :default_lang
4
+ attr_accessor :default_path
5
+ attr_accessor :excluded_states, :state_column, :keys_column
6
+
7
+
8
+ def initialize(filename, langs, args = {})
9
+ args.merge!({
10
+ :excluded_states => [],
11
+ :state_column => nil,
12
+ :keys_column => 0})
13
+
14
+ @csv_filename = filename
15
+ @langs = langs
16
+
17
+ if !@langs.is_a?(Hash) || @langs.size == 0
18
+ raise "wrong format or/and languages parameter" + @langs.inspect
19
+ end
20
+ @output_file = (@langs.size == 1) ? args[:output_file] : nil
21
+
22
+ @default_path = args[:default_path].to_s
23
+ @excluded_states = args[:excluded_states]
24
+ @state_column = args[:state_column]
25
+ @keys_column = args[:keys_column]
26
+ @default_lang = args[:default_lang]
27
+ end
28
+
29
+ def create_file_from_path(file_path)
30
+ path = File.dirname(file_path)
31
+ FileUtils.mkdir_p path
32
+ return File.new(file_path,"w")
33
+ end
34
+
35
+ def process_header(excludedCols, files, row, index)
36
+ files[index] = []
37
+ lang_index = row[index]
38
+
39
+ # create output files here
40
+ if @output_file
41
+ # one single file
42
+ files[index] << self.create_file_from_path(@output_file)
43
+ else
44
+ # create one file for each languages
45
+ if self.langs[lang_index].is_a?(Array)
46
+
47
+ self.langs[lang_index].each do |locale|
48
+ filename = self.file_path_for_locale(locale)
49
+ files[index] << self.create_file_from_path(filename)
50
+ end
51
+ elsif self.langs[lang_index].is_a?(String)
52
+ locale = self.langs[lang_index]
53
+ filename = self.file_path_for_locale(locale)
54
+ files[index] << self.create_file_from_path(filename)
55
+ else
56
+ raise "wrong format or/and languages parameter"
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ def file_path_for_locale(locale)
63
+ require 'pathname'
64
+ Pathname.new(self.default_path) + "#{locale}.lproj" + "Localizable.strings"
65
+ end
66
+
67
+ def process_value(row_value, default_value)
68
+ value = row_value.nil? ? default_value : row_value
69
+ value = "" if value.nil?
70
+ value.gsub!(/\\*\"/, "\\\"") #escape double quotes
71
+ value.gsub!(/\s*(\n|\\\s*n)\s*/, "\\n") #replace new lines with \n + strip
72
+ value.gsub!(/%\s+([a-zA-Z@])([^a-zA-Z@]|$)/, "%\\1\\2") #repair string formats ("% d points" etc)
73
+ value.gsub!(/([^0-9\s\(\{\[^])%/, "\\1 %")
74
+ value.strip!
75
+ return value
76
+ end
77
+
78
+ # Convert csv file to multiple Localizable.strings files for each column
79
+ def csv_to_dotstrings(name = self.csv_filename)
80
+ files = {}
81
+ rowIndex = 0
82
+ excludedCols = []
83
+ defaultCol = 0
84
+ nb_translations = 0
85
+
86
+ CSVParserClass.foreach(name, :quote_char => '"', :col_sep =>',', :row_sep => :auto) do |row|
87
+
88
+ if rowIndex == 0
89
+ return unless row.count > 1 #check there's at least two columns
90
+ else
91
+ next if row == nil or row[self.keys_column].nil? #skip empty lines (or sections)
92
+ end
93
+
94
+ row.size.times do |i|
95
+ next if excludedCols.include? i
96
+ if rowIndex == 0 #header
97
+ # ignore all headers not listed in langs to create files
98
+ (excludedCols << i and next) unless self.langs.has_key?(row[i])
99
+ self.process_header(excludedCols, files, row, i)
100
+ # define defaultCol
101
+ defaultCol = i if self.default_lang == row[i]
102
+ elsif !self.state_column || (row[self.state_column].nil? or row[self.state_column] == '' or !self.excluded_states.include? row[self.state_column])
103
+ # TODO: add option to strip the constant or referenced language
104
+ key = row[self.keys_column].strip
105
+ value = self.process_value(row[i], row[defaultCol])
106
+ # files for a given language, i.e could group english US with english UK.
107
+ localized_files = files[i]
108
+ if localized_files
109
+ localized_files.each do |file|
110
+ nb_translations += 1
111
+ file.write "\"#{key}\" = \"#{value}\";\n"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ rowIndex += 1
117
+ end
118
+ info = "Created #{files.size} files. Content: #{nb_translations} translations\n"
119
+ info += "List of created files:\n"
120
+
121
+ # closing I/O
122
+ files.each do |key,locale_files|
123
+ locale_files.each do |file|
124
+ info += "#{file.path.to_s}\n"
125
+ file.close
126
+ end
127
+ end
128
+ info
129
+ end # end of method
130
+
131
+ end # end of class
132
+
@@ -1,4 +1,13 @@
1
- require "google_drive"
1
+ # Faraday is a dependency of google_drive, this silents the warning
2
+ # see https://github.com/CocoaPods/CocoaPods/commit/f33f967427b857bf73645fd4d3f19eb05e9be0e0
3
+ # This is to make sure Faraday doesn't warn the user about the `system_timer` gem missing.
4
+ old_warn, $-w = $-w, nil
5
+ begin
6
+ require "google_drive"
7
+ ensure
8
+ $-w = old_warn
9
+ end
10
+
2
11
  class GoogleDoc
3
12
  attr_accessor :session
4
13
 
@@ -11,9 +20,9 @@ class GoogleDoc
11
20
  unless @session
12
21
  self.authenticate
13
22
  end
14
- result = @session.file_by_title(requested_filename)
23
+ result = @session.file_by_title(requested_filename)
15
24
  if result.is_a? Array
16
- file = result.first
25
+ file = result.first
17
26
  else
18
27
  file = result
19
28
  end
@@ -22,4 +31,4 @@ class GoogleDoc
22
31
  return output_filename
23
32
  end
24
33
 
25
- end
34
+ end
@@ -0,0 +1,96 @@
1
+ class Strings2CSV
2
+ # default_lang is the the column to refer to if a value is missing
3
+ # actually default_lang = default_filename
4
+ attr_accessor :csv_filename, :headers, :filenames, :default_lang
5
+
6
+ def initialize(args = {:filenames => []})
7
+ raise ArgumentError.new("No filenames given") unless args[:filenames]
8
+ if args[:headers]
9
+ raise ArgumentError.new("number of headers and files don't match, don't forget the constant column") unless args[:headers].size == (args[:filenames].size + 1)
10
+ end
11
+
12
+ @filenames = args[:filenames]
13
+
14
+ @csv_filename = args[:csv_filename] || "translations.csv"
15
+ @default_lang = args[:default_lang]
16
+ @headers = args[:headers] || self.default_headers
17
+ end
18
+
19
+ def default_headers
20
+ headers = ["Variables"]
21
+ @filenames.each do |fname|
22
+ headers << fname
23
+ end
24
+ headers
25
+ end
26
+
27
+
28
+ # Load all strings of a given file
29
+ def load_strings(strings_filename)
30
+ strings = ORDERED_HASH_CLASS.new
31
+ File.open(strings_filename, 'r') do |strings_file|
32
+ strings_file.read.each_line do |line|
33
+ hash = self.parse_dotstrings_line(line)
34
+ strings.merge!(hash) if hash
35
+ end
36
+ end
37
+ strings
38
+ end
39
+
40
+ def parse_dotstrings_line(line)
41
+ line.strip!
42
+ if (line[0] != ?# and line[0] != ?=)
43
+ m = line.match(/^[^\"]*\"(.+)\"[^=]+=[^\"]*\"(.*)\";/)
44
+ return {m[1] => m[2]} unless m.nil?
45
+ end
46
+ end
47
+
48
+
49
+ # Convert Localizable.strings files to one CSV file
50
+ # output: strings hash has filename for keys and the content of csv
51
+ def dotstrings_to_csv(write_to_file = true)
52
+ # Parse .strings files
53
+ strings = {}
54
+ keys = nil
55
+ lang_order = []
56
+
57
+ @filenames.each do |fname|
58
+ header = fname
59
+ strings[header] = load_strings(fname)
60
+ keys ||= strings[header].keys
61
+ end
62
+
63
+ if(write_to_file)
64
+ # Create csv file
65
+ puts "Creating #{@csv_filename}"
66
+ create_csv_file(keys, strings)
67
+ else
68
+ return keys, strings
69
+ end
70
+ end
71
+
72
+ def basename(file_path)
73
+ filename = File.basename(file_path)
74
+ return filename.split('.')[0].to_sym if file_path
75
+ end
76
+
77
+ # Create the resulting file
78
+ def create_csv_file(keys, strings)
79
+ raise "csv_filename must not be nil" unless self.csv_filename
80
+ CSVParserClass.open(self.csv_filename, "wb") do |csv|
81
+ csv << @headers
82
+ keys.each do |key|
83
+ line = [key]
84
+ default_val = strings[self.default_lang][key] if strings[self.default_lang]
85
+ @filenames.each do |fname|
86
+ lang = fname
87
+ current_val = strings[lang][key]
88
+ line << ((lang != self.default_lang and current_val == default_val) ? '' : current_val)
89
+ end
90
+ csv << line
91
+ end
92
+ puts "Done"
93
+ end
94
+ end
95
+
96
+ end