ad_localize 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/ad_localize +3 -0
- data/lib/ad_localize.rb +36 -0
- data/lib/ad_localize/ad_logger.rb +33 -0
- data/lib/ad_localize/constant.rb +22 -0
- data/lib/ad_localize/csv_file_manager.rb +46 -0
- data/lib/ad_localize/csv_parser.rb +127 -0
- data/lib/ad_localize/option_handler.rb +57 -0
- data/lib/ad_localize/platform/android_formatter.rb +68 -0
- data/lib/ad_localize/platform/ios_formatter.rb +69 -0
- data/lib/ad_localize/platform/json_formatter.rb +18 -0
- data/lib/ad_localize/platform/platform_formatter.rb +76 -0
- data/lib/ad_localize/platform/yml_formatter.rb +18 -0
- data/lib/ad_localize/runner.rb +45 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ae0a65677bc70e3175aae752514334aac36642bfb2a2dc2ce8e750f70bbf1d2
|
4
|
+
data.tar.gz: 30fb7aa05e9b7b6d27f608ec8d9c712f71be21be91560f082cb13f935167e258
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f9e675d6438e1883632aa685e74166021fbe71e75ae1de4b4fda6d93b5c3705b177e0b19dc7399737261d97499c6521c5f746228e021df5401f97ef3fa57ccd6
|
7
|
+
data.tar.gz: 86b8d0efe4d91fe6eebb6849a76e1bb090c797de13ce3d430fb8a49efa7c93986626a1bb31bfccc639b19006ab7f6edae8c60e339f36b3aeb5d8734fc3e5b024
|
data/bin/ad_localize
ADDED
data/lib/ad_localize.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'byebug'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'pathname'
|
8
|
+
require 'yaml'
|
9
|
+
require 'logger'
|
10
|
+
require 'colorize'
|
11
|
+
require 'csv'
|
12
|
+
require 'active_support'
|
13
|
+
require 'open-uri'
|
14
|
+
require 'optparse'
|
15
|
+
require 'json'
|
16
|
+
require 'nokogiri'
|
17
|
+
|
18
|
+
require 'ad_localize/ad_logger'
|
19
|
+
require 'ad_localize/constant'
|
20
|
+
require 'ad_localize/csv_file_manager'
|
21
|
+
require 'ad_localize/csv_parser'
|
22
|
+
require 'ad_localize/option_handler'
|
23
|
+
require 'ad_localize/runner'
|
24
|
+
require 'ad_localize/platform/platform_formatter'
|
25
|
+
require 'ad_localize/platform/android_formatter'
|
26
|
+
require 'ad_localize/platform/ios_formatter'
|
27
|
+
require 'ad_localize/platform/json_formatter'
|
28
|
+
require 'ad_localize/platform/yml_formatter'
|
29
|
+
|
30
|
+
module AdLocalize
|
31
|
+
LOGGER = AdLogger.new
|
32
|
+
|
33
|
+
def self.run
|
34
|
+
Runner.new.run
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
class AdLogger
|
3
|
+
SEVERITY = { debug: Logger::DEBUG , info: Logger::INFO, warn: Logger::WARN, error: Logger::ERROR, fatal: Logger::FATAL, unknown: Logger::UNKNOWN }
|
4
|
+
AUTHORIZED_COLORS = [:black, :yellow, :red, :blue, :green]
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@logger = Logger.new(STDOUT)
|
8
|
+
@logger.level = Logger::INFO
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(level, color, text)
|
12
|
+
exit unless text and level and color
|
13
|
+
|
14
|
+
level, color = [level, color].map(&:to_sym)
|
15
|
+
raise "Color not supported" unless AUTHORIZED_COLORS.include? color
|
16
|
+
raise "Invalid severity" unless SEVERITY.dig(level)
|
17
|
+
|
18
|
+
@logger.add(SEVERITY.dig(level), text.send(color))
|
19
|
+
end
|
20
|
+
|
21
|
+
def debug!
|
22
|
+
@logger.level = Logger::DEBUG
|
23
|
+
end
|
24
|
+
|
25
|
+
def debug?
|
26
|
+
@logger.debug?
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
@logger.close
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
module Constant
|
3
|
+
SUPPORTED_PLATFORMS = %w(ios android yml json)
|
4
|
+
PLURAL_KEY_SYMBOL = :plural
|
5
|
+
SINGULAR_KEY_SYMBOL = :singular
|
6
|
+
COMMENT_KEY_SYMBOL = :comment
|
7
|
+
COMMENT_KEY_COLUMN_IDENTIFIER = "comment"
|
8
|
+
DEFAULT_CONFIG = {
|
9
|
+
platforms: {
|
10
|
+
export_directory_names: {
|
11
|
+
ios: "%{locale}.lproj",
|
12
|
+
android: "values%{locale}"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
CONFIG = YAML.load_file(Pathname::pwd + 'config.yml').deep_symbolize_keys rescue DEFAULT_CONFIG
|
17
|
+
EXPORT_FOLDER = 'exports'
|
18
|
+
IOS_SINGULAR_EXPORT_FILENAME = "Localizable.strings"
|
19
|
+
IOS_PLURAL_EXPORT_FILENAME = "Localizable.stringsdict"
|
20
|
+
ANDROID_EXPORT_FILENAME = "strings.xml"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
class CsvFileManager
|
3
|
+
CSV_CONTENT_TYPES = %w(text/csv text/plain)
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def csv?(file)
|
7
|
+
!file.nil? && CSV_CONTENT_TYPES.include?(`file --brief --mime-type '#{file}'`.strip)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the downloaded file name (it is located in the current directory)
|
11
|
+
def download_from_drive(key)
|
12
|
+
LOGGER.log(:info, :black, "Downloading file from google drive...")
|
13
|
+
download_string_path = "./#{key}.csv"
|
14
|
+
begin
|
15
|
+
File.open(download_string_path, "wb") do |saved_file|
|
16
|
+
# the following "open" is provided by open-uri
|
17
|
+
open(drive_download_url(key), "rb") do |read_file|
|
18
|
+
saved_file.write(read_file.read)
|
19
|
+
end
|
20
|
+
File.basename saved_file
|
21
|
+
end
|
22
|
+
rescue => e
|
23
|
+
delete_drive_file(download_string_path)
|
24
|
+
LOGGER.log(:error, :red, e.message)
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def select_csvs(files)
|
30
|
+
files.select do |file|
|
31
|
+
LOGGER.log(:error, :red, "#{file} is not a csv. It will be ignored") unless self.csv?(file)
|
32
|
+
self.csv?(file)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_drive_file(file)
|
37
|
+
Pathname.new(file).delete unless file.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def drive_download_url(key)
|
42
|
+
"https://docs.google.com/spreadsheets/d/#{key}/export?format=csv&id=#{key}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
class CsvParser
|
3
|
+
CSV_WORDING_KEYS_COLUMN = "key"
|
4
|
+
PLURAL_KEY_REGEXP = /\#\#\{(\w+)\}/
|
5
|
+
|
6
|
+
attr_accessor :locales
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@locales = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raise a StandardError if no locale is detected in the csv
|
13
|
+
def extract_data(file_name)
|
14
|
+
data = {}
|
15
|
+
CSV.foreach(file_name, headers: true, skip_blanks: true) do |row|
|
16
|
+
validity_status = check_row(row)
|
17
|
+
if validity_status.zero?
|
18
|
+
next
|
19
|
+
elsif validity_status == -1
|
20
|
+
exit
|
21
|
+
else
|
22
|
+
find_locales(row) if locales.empty?
|
23
|
+
row_data = parse_row(row)
|
24
|
+
data.deep_merge!(row_data) unless row_data.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
data
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
# Returns 1 if row is ok, 0 if row there are missing information and -1 if row is not a csv row
|
32
|
+
def check_row(row)
|
33
|
+
valid_row = 1
|
34
|
+
# Check non empty row
|
35
|
+
if row.field(CSV_WORDING_KEYS_COLUMN).nil?
|
36
|
+
LOGGER.log(:error, :red, "Missing key in line #{$.}") unless row.fields.all?(&:nil?)
|
37
|
+
valid_row = 0
|
38
|
+
elsif not row.headers.include?(CSV_WORDING_KEYS_COLUMN)
|
39
|
+
LOGGER.log(:error, :red, "[CSV FORMAT] #{file_name} is not a valid file")
|
40
|
+
valid_row = -1
|
41
|
+
end
|
42
|
+
return valid_row
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_locales(row)
|
46
|
+
wording_key_column_index = row.index(CSV_WORDING_KEYS_COLUMN)
|
47
|
+
if row.length > wording_key_column_index
|
48
|
+
self.locales = row.headers.slice((wording_key_column_index+1)..-1).compact.reject do |header|
|
49
|
+
header.to_s.include? Constant::COMMENT_KEY_COLUMN_IDENTIFIER
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if locales.empty?
|
53
|
+
raise 'NO DETECTED LOCALES'
|
54
|
+
else
|
55
|
+
LOGGER.log(:debug, :black, "DETECTED LOCALES : #{locales.join(', ')}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_row(row)
|
60
|
+
key_infos = parse_key(row)
|
61
|
+
return nil if key_infos.nil?
|
62
|
+
|
63
|
+
locales.each_with_object({ key_infos.dig(:key) => {} }) do |locale, output|
|
64
|
+
current_key = key_infos.dig(:key)
|
65
|
+
current_key_already_has_wording_for_locale = output[current_key].key? locale.to_sym
|
66
|
+
output[current_key][locale.to_sym] = { key_infos.dig(:numeral_key) => {} } unless current_key_already_has_wording_for_locale
|
67
|
+
|
68
|
+
if key_infos.dig(:numeral_key) == Constant::SINGULAR_KEY_SYMBOL
|
69
|
+
trace_wording(row[locale], "[#{locale.upcase}] Singular key ---> #{current_key}", "[#{locale.upcase}] Missing translation for #{current_key}")
|
70
|
+
value = row[locale] || default_wording(locale, current_key)
|
71
|
+
elsif key_infos.dig(:numeral_key) == Constant::PLURAL_KEY_SYMBOL
|
72
|
+
trace_wording(row[locale],
|
73
|
+
"[#{locale.upcase}] Plural key ---> plural_identifier : #{key_infos.dig(:plural_identifier)}, key : #{current_key}",
|
74
|
+
"[#{locale.upcase}] Missing translation for #{current_key} (#{key_infos.dig(:plural_identifier)})")
|
75
|
+
value = { key_infos.dig(:plural_identifier) => row[locale] || default_wording(locale, "#{current_key} (#{key_infos.dig(:plural_identifier)})") }
|
76
|
+
else
|
77
|
+
raise "Unknown numeral key #{key_infos.dig(:numeral_key)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
check_wording_parameters(row[locale], locale, current_key)
|
81
|
+
output[current_key][locale.to_sym][key_infos.dig(:numeral_key)] = value
|
82
|
+
locale_comment_column = "#{Constant::COMMENT_KEY_COLUMN_IDENTIFIER} #{locale}"
|
83
|
+
output[current_key][locale.to_sym][Constant::COMMENT_KEY_SYMBOL] = row[locale_comment_column] unless locale_comment_column.blank? and row[locale_comment_column].blank?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_key(row)
|
88
|
+
key = row.field(CSV_WORDING_KEYS_COLUMN)
|
89
|
+
plural_prefix = key.match(PLURAL_KEY_REGEXP)
|
90
|
+
plural_identifier = nil
|
91
|
+
invalid_plural = false
|
92
|
+
|
93
|
+
if plural_prefix.nil?
|
94
|
+
numeral_key = Constant::SINGULAR_KEY_SYMBOL
|
95
|
+
else
|
96
|
+
numeral_key = Constant::PLURAL_KEY_SYMBOL
|
97
|
+
key = plural_prefix.pre_match
|
98
|
+
plural_identifier = plural_prefix.captures&.first
|
99
|
+
LOGGER.log(:debug, :red, "Invalid key #{key}") if key.nil?
|
100
|
+
LOGGER.log(:debug, :red, "Empty plural prefix!") if plural_identifier.nil?
|
101
|
+
invalid_plural = plural_identifier.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
(key.nil? or invalid_plural) ? nil : { key: key.to_sym, numeral_key: numeral_key, plural_identifier: plural_identifier&.to_sym }
|
105
|
+
end
|
106
|
+
|
107
|
+
def default_wording(locale, key)
|
108
|
+
LOGGER.debug? ? "<[#{locale.upcase}] Missing translation for #{key}>" : ""
|
109
|
+
end
|
110
|
+
|
111
|
+
def trace_wording(wording, present_message, missing_message)
|
112
|
+
if wording
|
113
|
+
LOGGER.log(:debug, :black, present_message)
|
114
|
+
else
|
115
|
+
LOGGER.log(:debug, :yellow, missing_message)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_wording_parameters(value, locale, key)
|
120
|
+
formatted_arguments = value&.scan(/%(\d$)?@/) || []
|
121
|
+
if formatted_arguments.size >= 2
|
122
|
+
is_all_ordered = formatted_arguments.inject(true){ |is_ordered, match| is_ordered &&= !match.join.empty? }
|
123
|
+
LOGGER.log(:warn, :red, "[#{locale.upcase}] Multiple arguments for #{key} but no order") unless is_all_ordered
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
class OptionHandler
|
3
|
+
GOOGLE_DRIVE_DOCUMENT_ID = { length: 32, regexp: /\A[\w-]+\Z/ }
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def parse
|
7
|
+
args = { debug: false, only: nil}
|
8
|
+
|
9
|
+
opt_parser = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: ruby bin/export [options] file(s)"
|
11
|
+
|
12
|
+
|
13
|
+
opts.on("-h", "--help", "Prints help") do
|
14
|
+
puts opts
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
opts.on("-d", "--debug", "Run in debug mode") do
|
18
|
+
args[:debug] = true
|
19
|
+
LOGGER.debug!
|
20
|
+
end
|
21
|
+
opts.on("-k", "--drive-key #{GOOGLE_DRIVE_DOCUMENT_ID.dig(:length)}_characters", String, "Use google drive spreadsheets") do |key|
|
22
|
+
is_valid_drive_key = !!(key =~ GOOGLE_DRIVE_DOCUMENT_ID.dig(:regexp)) && (key.size >= GOOGLE_DRIVE_DOCUMENT_ID.dig(:length))
|
23
|
+
args[:drive_key] = is_valid_drive_key ? key : nil
|
24
|
+
end
|
25
|
+
opts.on("-o", "--only platform1,platform2", Array, "Only generate localisation files for the specified platforms. Supported platforms : #{Constant::SUPPORTED_PLATFORMS.join(', ')}") do |platforms|
|
26
|
+
args[:only] = filter_option_args("-o", platforms) { |platform| !!Constant::SUPPORTED_PLATFORMS.index(platform) }
|
27
|
+
end
|
28
|
+
opts.on("-t", "--target-dir PATH", String, "Path to the target directory") do |output_path|
|
29
|
+
pn = Pathname.new(output_path)
|
30
|
+
if pn.directory? and pn.readable? and pn.writable?
|
31
|
+
args[:output_path] = output_path
|
32
|
+
else
|
33
|
+
raise ArgumentError.new("Invalid target directory. Check the permissions")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
opt_parser.parse!
|
40
|
+
rescue OptionParser::MissingArgument => e
|
41
|
+
LOGGER.log(:error, :red, "Missing argument for option #{e.args.join(',')}")
|
42
|
+
rescue ArgumentError => e
|
43
|
+
LOGGER.log(:error, :red, e.message)
|
44
|
+
end
|
45
|
+
args
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def filter_option_args(option, args)
|
50
|
+
result = args.select { |arg| yield(arg) }
|
51
|
+
invalid_args = args - result
|
52
|
+
LOGGER.log(:debug, :red, "Invalid arg(s) for option #{option}: #{invalid_args.join(', ')}") unless invalid_args.empty?
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module AdLocalize::Platform
|
2
|
+
class AndroidFormatter < PlatformFormatter
|
3
|
+
def platform
|
4
|
+
:android
|
5
|
+
end
|
6
|
+
|
7
|
+
def export(locale, data, export_extension = nil, substitution_format = nil)
|
8
|
+
locale = locale.to_sym
|
9
|
+
export_dir_suffix = (locale == default_locale) ? "" : "-#{locale.downcase}"
|
10
|
+
create_locale_dir(export_dir_suffix)
|
11
|
+
|
12
|
+
xml_doc = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
13
|
+
xml.resources {
|
14
|
+
data.each do |key, wording|
|
15
|
+
singular_wording = wording.dig(locale, AdLocalize::Constant::SINGULAR_KEY_SYMBOL)
|
16
|
+
add_singular_wording_to_xml(key, singular_wording, xml) unless singular_wording.blank?
|
17
|
+
|
18
|
+
plural_wording = wording.dig(locale, AdLocalize::Constant::PLURAL_KEY_SYMBOL)
|
19
|
+
add_plural_wording_to_xml(key, plural_wording, xml) unless plural_wording.blank?
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
export_dir(export_dir_suffix).join(AdLocalize::Constant::ANDROID_EXPORT_FILENAME).open("w") do |file|
|
25
|
+
file.puts xml_doc.to_xml(indent: 4)
|
26
|
+
end
|
27
|
+
AdLocalize::LOGGER.log(:debug, :black, "Android [#{locale}] ---> DONE!")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def ios_converter(value)
|
32
|
+
processedValue = value.gsub(/</, "<")
|
33
|
+
processedValue = processedValue.gsub(/>/, ">")
|
34
|
+
processedValue = processedValue.gsub(/(?<!\\)'/, "\\\\'")
|
35
|
+
processedValue = processedValue.gsub(/(?<!\\)\"/, "\\\"")
|
36
|
+
processedValue = processedValue.gsub(/&(?!(?:amp|lt|gt|quot|apos);)/, '&')
|
37
|
+
processedValue = processedValue.gsub(/(%(\d+\$)?@)/, '%\2s') # should match values like %1$s and %s
|
38
|
+
processedValue = processedValue.gsub(/(%((\d+\$)?(\d+)?)i)/, '%\2d') # should match values like %i, %3$i, %01i, %1$02i
|
39
|
+
processedValue = processedValue.gsub(/%(?!((\d+\$)?(s|(\d+)?d)))/, '%%') # negative lookahead: identifies when user really wants to display a %
|
40
|
+
processedValue = processedValue.gsub("\\U", "\\u")
|
41
|
+
"\"#{processedValue}\""
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_singular_wording_to_xml(key, text, xml)
|
45
|
+
xml.string(name: key) {
|
46
|
+
add_wording_text_to_xml(text, xml)
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_plural_wording_to_xml(key, plural_hash, xml)
|
51
|
+
xml.plurals(name: key) {
|
52
|
+
plural_hash.each do |plural_type, plural_text|
|
53
|
+
next if plural_text.blank?
|
54
|
+
xml.item(quantity: plural_type) {
|
55
|
+
add_wording_text_to_xml(plural_text, xml)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_wording_text_to_xml(wording_text, xml)
|
62
|
+
raise 'Wording text should not be empty' if wording_text.blank?
|
63
|
+
xml.text(
|
64
|
+
ios_converter(wording_text)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AdLocalize::Platform
|
2
|
+
class IosFormatter < PlatformFormatter
|
3
|
+
def platform
|
4
|
+
:ios
|
5
|
+
end
|
6
|
+
|
7
|
+
def export(locale, data, export_extension = nil, substitution_format = nil)
|
8
|
+
create_locale_dir(locale)
|
9
|
+
[AdLocalize::Constant::PLURAL_KEY_SYMBOL, AdLocalize::Constant::SINGULAR_KEY_SYMBOL].each do |numeral_key|
|
10
|
+
numeral_data = data.select {|key, wording| wording.dig(locale.to_sym)&.key? numeral_key}
|
11
|
+
if numeral_data.empty?
|
12
|
+
AdLocalize::LOGGER.log(:info, :black, "[#{locale.upcase}] no #{numeral_key.to_s} keys were found to generate the file")
|
13
|
+
else
|
14
|
+
send("write_#{numeral_key}", locale, numeral_data)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def write_singular(locale, singulars)
|
21
|
+
locale = locale.to_sym
|
22
|
+
|
23
|
+
singulars.each do |key, locales_wording|
|
24
|
+
value = locales_wording.dig(locale, AdLocalize::Constant::SINGULAR_KEY_SYMBOL)
|
25
|
+
comment = locales_wording.dig(locale, AdLocalize::Constant::COMMENT_KEY_SYMBOL)
|
26
|
+
export_dir(locale).join(AdLocalize::Constant::IOS_SINGULAR_EXPORT_FILENAME).open("a") do |file|
|
27
|
+
line = "\"#{key}\" = \"#{value}\";"
|
28
|
+
line << " // #{comment}" unless comment.nil?
|
29
|
+
line << "\n"
|
30
|
+
file.puts line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
AdLocalize::LOGGER.log(:debug, :black, "iOS singular [#{locale}] ---> DONE!")
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_plural(locale, plurals)
|
37
|
+
locale = locale.to_sym
|
38
|
+
|
39
|
+
xml_doc = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
40
|
+
xml.plist {
|
41
|
+
xml.dict {
|
42
|
+
plurals.each do |wording_key, translations|
|
43
|
+
xml.key wording_key
|
44
|
+
xml.dict {
|
45
|
+
xml.key "NSStringLocalizedFormatKey"
|
46
|
+
xml.string "%\#@key@"
|
47
|
+
xml.key "key"
|
48
|
+
xml.dict {
|
49
|
+
xml.key "NSStringFormatSpecTypeKey"
|
50
|
+
xml.string "NSStringPluralRuleType"
|
51
|
+
xml.key "NSStringFormatValueTypeKey"
|
52
|
+
xml.string "d"
|
53
|
+
translations[locale][AdLocalize::Constant::PLURAL_KEY_SYMBOL].each do |wording_type, wording_value|
|
54
|
+
xml.key wording_type
|
55
|
+
xml.string wording_value
|
56
|
+
end
|
57
|
+
}
|
58
|
+
}
|
59
|
+
end
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
export_dir(locale).join(AdLocalize::Constant::IOS_PLURAL_EXPORT_FILENAME).open("w") do |file|
|
64
|
+
file.puts xml_doc.to_xml(indent: 4)
|
65
|
+
end
|
66
|
+
AdLocalize::LOGGER.log(:debug, :black, "iOS plural [#{locale}] ---> DONE!")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AdLocalize::Platform
|
2
|
+
class JsonFormatter < PlatformFormatter
|
3
|
+
def platform
|
4
|
+
:json
|
5
|
+
end
|
6
|
+
|
7
|
+
def export(locale, data, export_extension = "json", substitution_format = "angular")
|
8
|
+
super(locale, data, export_extension, substitution_format) do |json_data, file|
|
9
|
+
file.puts json_data.to_json
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
def ios_converter(value)
|
15
|
+
value.gsub(/(%(?!)?(\d+\$)?[@isd])/, "VARIABLE_TO_SET")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module AdLocalize::Platform
|
2
|
+
class PlatformFormatter
|
3
|
+
PLURAL_KEY_SYMBOL = :plural
|
4
|
+
SINGULAR_KEY_SYMBOL = :singular
|
5
|
+
OPTIONS = {output_path: ".."}
|
6
|
+
|
7
|
+
attr_accessor :platform_dir
|
8
|
+
attr_reader :default_locale, :output_path
|
9
|
+
|
10
|
+
def initialize(default_locale, output_path)
|
11
|
+
@default_locale = default_locale.to_sym
|
12
|
+
@output_path = output_path # Must be set before platform_dir
|
13
|
+
@platform_dir = export_base_directory.join(platform.to_s)
|
14
|
+
create_platform_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def platform
|
18
|
+
raise 'Please override me!'
|
19
|
+
end
|
20
|
+
|
21
|
+
def export(locale, data, export_extension, substitution_format)
|
22
|
+
locale = locale.to_sym
|
23
|
+
formatted_data = data.each_with_object({}) do |(key, wording), hash_acc|
|
24
|
+
hash_acc[locale.to_s] = {} unless hash_acc.key? locale.to_s
|
25
|
+
if wording.dig(locale)&.key? :singular
|
26
|
+
value = ios_converter(wording.dig(locale, :singular))
|
27
|
+
hash_acc[locale.to_s][key.to_s] = value
|
28
|
+
end
|
29
|
+
if wording.dig(locale)&.key? :plural
|
30
|
+
hash_acc[locale.to_s][key.to_s] = {} unless hash_acc[locale.to_s].key? key.to_s
|
31
|
+
wording.dig(locale, :plural).each do |plural_type, plural_text|
|
32
|
+
value = ios_converter(plural_text)
|
33
|
+
hash_acc[locale.to_s][key.to_s][plural_type.to_s] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
platform_dir.join("#{locale.to_s}.#{export_extension}").open("w") do |file|
|
39
|
+
yield(formatted_data, file)
|
40
|
+
end
|
41
|
+
|
42
|
+
AdLocalize::LOGGER.log(:debug, :black, "#{platform.to_s.upcase} [#{locale}] ---> DONE!")
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def export_base_directory
|
47
|
+
if output_path
|
48
|
+
Pathname.new(output_path)
|
49
|
+
else
|
50
|
+
Pathname::pwd.join(AdLocalize::Constant::EXPORT_FOLDER)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_platform_dir
|
55
|
+
if platform_dir.directory?
|
56
|
+
FileUtils.rm_rf("#{platform_dir}/.", secure: true)
|
57
|
+
else
|
58
|
+
platform_dir.mkpath
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def export_dir(locale)
|
63
|
+
platform_dir + AdLocalize::Constant::CONFIG.dig(:platforms, :export_directory_names, platform.to_sym) % { locale: locale.downcase }
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_locale_dir(locale)
|
67
|
+
raise 'Locale needed' unless locale
|
68
|
+
|
69
|
+
if export_dir(locale).directory?
|
70
|
+
FileUtils.rm_rf("#{export_dir(locale)}/.", secure: true)
|
71
|
+
else
|
72
|
+
export_dir(locale).mkdir
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AdLocalize::Platform
|
2
|
+
class YmlFormatter < PlatformFormatter
|
3
|
+
def platform
|
4
|
+
:yml
|
5
|
+
end
|
6
|
+
|
7
|
+
def export(locale, data, export_extension = "yml", substitution_format = "yml")
|
8
|
+
super(locale, data, export_extension, substitution_format) do |yml_data, file|
|
9
|
+
file.puts yml_data.to_yaml
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
def ios_converter(value)
|
15
|
+
value.gsub(/(%(?!)?(\d+\$)?[@isd])/, "VARIABLE_TO_SET")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module AdLocalize
|
2
|
+
class Runner
|
3
|
+
attr_accessor :options
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@options = OptionHandler.parse
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(args = ARGV)
|
10
|
+
LOGGER.log(:info, :black, "OPTIONS : #{options}")
|
11
|
+
input_files = (args + [options.dig(:drive_key)]).compact # drive_key can be nil
|
12
|
+
if input_files.length.zero?
|
13
|
+
LOGGER.log(:error, :red, "No CSV to parse. Use option -h to see how to use this script")
|
14
|
+
else
|
15
|
+
file_to_parse = args.first
|
16
|
+
LOGGER.log(:warn, :yellow, "Only one CSV can be treated - the priority goes to #{file_to_parse}") if input_files.length > 1
|
17
|
+
if args.empty?
|
18
|
+
options[:drive_file] = CsvFileManager.download_from_drive(options.dig(:drive_key))
|
19
|
+
file_to_parse = options.dig(:drive_file)
|
20
|
+
end
|
21
|
+
CsvFileManager.csv?(file_to_parse) ? export(file_to_parse) : LOGGER.log(:error, :red, "#{file_to_parse} is not a csv")
|
22
|
+
CsvFileManager.delete_drive_file(options[:drive_file]) if options[:drive_file]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def export(file)
|
28
|
+
LOGGER.log(:info, :black, "********* PARSING #{file} *********")
|
29
|
+
LOGGER.log(:info, :black, "Extracting data from file...")
|
30
|
+
parser = CsvParser.new
|
31
|
+
data = parser.extract_data(file)
|
32
|
+
if data.empty?
|
33
|
+
LOGGER.log(:error, :red, "No data were found in the file - cannot start the file generation process")
|
34
|
+
else
|
35
|
+
export_platforms = options.dig(:only) || Constant::SUPPORTED_PLATFORMS
|
36
|
+
export_platforms.each do |platform|
|
37
|
+
platform_formatter = "AdLocalize::Platform::#{platform.to_s.camelize}Formatter".constantize.new(parser.locales.first, options.dig(:output_path))
|
38
|
+
parser.locales.each do |locale|
|
39
|
+
platform_formatter.export(locale, data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ad_localize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edouard Siegel
|
8
|
+
- Patrick Nollet
|
9
|
+
- Adrien Couque
|
10
|
+
- Tarek Belkahia
|
11
|
+
- Hugo Hache
|
12
|
+
- Vincent Brison
|
13
|
+
- Joanna Vigné
|
14
|
+
- Nicolas Braun
|
15
|
+
- Corentin Huard
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bundler
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.16'
|
28
|
+
type: :development
|
29
|
+
prerelease: false
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.16'
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rake
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
type: :development
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '10.0'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: minitest
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '5.0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '5.0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: byebug
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '10.0'
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: json
|
79
|
+
requirement: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.8'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.8.3
|
87
|
+
type: :runtime
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.8'
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.8.3
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: nokogiri
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.8'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.8.2
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '1.8'
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 1.8.2
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: activesupport
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '5.1'
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 5.1.5
|
127
|
+
type: :runtime
|
128
|
+
prerelease: false
|
129
|
+
version_requirements: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - "~>"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '5.1'
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 5.1.5
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: colorize
|
139
|
+
requirement: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - "~>"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: 0.8.1
|
144
|
+
type: :runtime
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - "~>"
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: 0.8.1
|
151
|
+
description: " Convert a wording file in localization files. Supported export formats
|
152
|
+
are : iOS, Android,\n JSON and YAML "
|
153
|
+
email:
|
154
|
+
- joanna.vigne@fabernovel.com
|
155
|
+
- hugo.hache@fabernovel.com
|
156
|
+
- edouard.siegel@fabernovel.com
|
157
|
+
executables:
|
158
|
+
- ad_localize
|
159
|
+
extensions: []
|
160
|
+
extra_rdoc_files: []
|
161
|
+
files:
|
162
|
+
- bin/ad_localize
|
163
|
+
- lib/ad_localize.rb
|
164
|
+
- lib/ad_localize/ad_logger.rb
|
165
|
+
- lib/ad_localize/constant.rb
|
166
|
+
- lib/ad_localize/csv_file_manager.rb
|
167
|
+
- lib/ad_localize/csv_parser.rb
|
168
|
+
- lib/ad_localize/option_handler.rb
|
169
|
+
- lib/ad_localize/platform/android_formatter.rb
|
170
|
+
- lib/ad_localize/platform/ios_formatter.rb
|
171
|
+
- lib/ad_localize/platform/json_formatter.rb
|
172
|
+
- lib/ad_localize/platform/platform_formatter.rb
|
173
|
+
- lib/ad_localize/platform/yml_formatter.rb
|
174
|
+
- lib/ad_localize/runner.rb
|
175
|
+
homepage: https://technologies.fabernovel.com
|
176
|
+
licenses:
|
177
|
+
- MIT
|
178
|
+
metadata: {}
|
179
|
+
post_install_message:
|
180
|
+
rdoc_options: []
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
requirements: []
|
194
|
+
rubyforge_project:
|
195
|
+
rubygems_version: 2.7.3
|
196
|
+
signing_key:
|
197
|
+
specification_version: 4
|
198
|
+
summary: AdLocalize
|
199
|
+
test_files: []
|