ad_localize 3.1.0 → 3.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9510eed71bfba2c01f047d80707eaaeffbe24d6fefc4f65927b1afe8e44c2a49
4
- data.tar.gz: 3aba4a56d7f7b5fd0da6056471c55cf025a9133dd3d5a815fe06ac89a662d645
3
+ metadata.gz: 736260ad38d18773bf3b8ed6441534bb3467f49edeae2335c7ec5b8742f934e9
4
+ data.tar.gz: 38cb4f5d8647cc997dc2eb0c784b42469ae5dbb51aad9ba8f19f97907654a181
5
5
  SHA512:
6
- metadata.gz: b87614c092bd2c6fcee5deeba77add9035f01c4861d526081e91606e6613827f4199d2fccb1f69aa7149f6c7a2755536d7e7a2b5d50901455b519fc20a98d695
7
- data.tar.gz: 22b0cc2b7b3a6290017b7cdd9e8bb0a7bb30b662bdeada9669d9fd10a670af0f3d4e99919fca9023b0cd289d0ad254d45e14dc7d7fdc20f0b6fac25f92017eca
6
+ metadata.gz: 5448a25049abad18258c563bd84809a3f38cfefa54a60ace5c80fd7f214af81dcb30effb3a1d907962e6fb7d5707de322159e7361ef80ae01925124914d1a9ac
7
+ data.tar.gz: a139d77e6c76386493c3cb8d7d39aaf2c3a9526cba30a05b9aff2adaaccf326cb71303919a888da7329b4d56bef72f51211b0ee7fd3aa21e1641e0156ea0461e
@@ -0,0 +1,20 @@
1
+ name: Ruby
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v1
12
+ - name: Set up Ruby 2.6
13
+ uses: actions/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6.x
16
+ - name: Build and test with Rake
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs 4 --retry 3
20
+ bundle exec rake
data/.gitignore CHANGED
@@ -9,4 +9,5 @@ ad_localize/tmp/
9
9
  .idea
10
10
  .byebug_history
11
11
  .ruby-version
12
-
12
+ pkg/
13
+ exports/
@@ -4,9 +4,50 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [3.6.0] - 2020-06-23
8
+ ### Added
9
+ - add documentation for service account usage by [@sjcqs](https://github.com/sjcqs)
10
+ - add compatibility with activesupport 6 by [@Hugo-Hache](https://github.com/Hugo-Hache)
11
+
12
+ ### Changed
13
+
14
+ ### Fixed
15
+
16
+ ## [3.5.0] - 2020-05-12
17
+ ### Added
18
+ - add support for private spreadsheet using google service acccount by [@sjcqs](https://github.com/sjcqs). Fixes [#31](https://github.com/applidium/ad_localize/issues/31)
19
+ - add makefile for easier testing by [@felginep](https://github.com/felginep)
20
+
21
+ ### Changed
22
+ - improve error message to have useful information in case of google spreadsheet use by [@felginep](https://github.com/felginep). Fixes [#27](https://github.com/applidium/ad_localize/issues/27)
23
+ - platform folder is no longer generated when there is only one platform selected. The files are directly generated in the output path. By [@felginep](https://github.com/felginep). Fixes [#29](https://github.com/applidium/ad_localize/issues/29)
24
+ - raise error when google spreadsheet key is invalid by [@felginep](https://github.com/felginep)
25
+
26
+ ### Fixed
27
+ - auto escape strings in Localizable.strings by [@felginep](https://github.com/felginep). Fixes [#26](https://github.com/applidium/ad_localize/issues/26)
28
+ - trim keys to prevent user error by [@felginep](https://github.com/felginep). Fixes [#16](https://github.com/applidium/ad_localize/issues/16)
29
+
30
+ ## [3.4.0] - 2019-02-10
31
+ ### Added
32
+ - Rails folks, [@epaillous](https://github.com/epaillous) has improved the YAML support. You can now have multi-level wording.
33
+
34
+ ## [3.3.0] - 2019-02-10
35
+ ### Added
36
+ - improve React support using keys with dots to generate nested JSON files by [@epaillous](https://github.com/epaillous)
37
+
38
+ ## [3.2.0] - 2019-12-10
39
+ ### Added
40
+ - Add tests to compare reference exports by [@felginep](https://github.com/felginep)
41
+ - [iOS only] Handle [adaptive strings](https://developer.apple.com/documentation/foundation/nsstring/1413104-variantfittingpresentationwidth) by [@felginep](https://github.com/felginep)
42
+
43
+ ### Changed
44
+ - Fix issue with Info.plist format by [@felginep](https://github.com/felginep)
45
+ - Do not export plurals for android when there are no values by [@felginep](https://github.com/felginep)
46
+
7
47
  ## [3.1.0] - 2019-09-30
8
48
  ### Added
9
49
  - [iOS only] Info.plist generation support by [@felginep](https://github.com/felginep)
50
+
10
51
  ### Changed
11
52
  - No more warning log for empty lines by [@felginep](https://github.com/felginep)
12
53
 
@@ -27,4 +68,3 @@ TODO
27
68
 
28
69
  ## [0.1.0] - 2016-04-25
29
70
  TODO
30
-
@@ -0,0 +1,78 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ad_localize (3.5.0)
5
+ activesupport (>= 5.2, < 7.0)
6
+ colorize (~> 0.8)
7
+ googleauth (~> 0.12)
8
+ nokogiri (~> 1.10)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (6.0.3.1)
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ i18n (>= 0.7, < 2)
16
+ minitest (~> 5.1)
17
+ tzinfo (~> 1.1)
18
+ zeitwerk (~> 2.2, >= 2.2.2)
19
+ addressable (2.7.0)
20
+ public_suffix (>= 2.0.2, < 5.0)
21
+ ansi (1.5.0)
22
+ builder (3.2.4)
23
+ byebug (11.1.3)
24
+ colorize (0.8.1)
25
+ concurrent-ruby (1.1.6)
26
+ diffy (3.3.0)
27
+ faraday (1.0.1)
28
+ multipart-post (>= 1.2, < 3)
29
+ googleauth (0.12.0)
30
+ faraday (>= 0.17.3, < 2.0)
31
+ jwt (>= 1.4, < 3.0)
32
+ memoist (~> 0.16)
33
+ multi_json (~> 1.11)
34
+ os (>= 0.9, < 2.0)
35
+ signet (~> 0.14)
36
+ i18n (1.8.2)
37
+ concurrent-ruby (~> 1.0)
38
+ jwt (2.2.1)
39
+ memoist (0.16.2)
40
+ mini_portile2 (2.4.0)
41
+ minitest (5.14.0)
42
+ minitest-reporters (1.4.2)
43
+ ansi
44
+ builder
45
+ minitest (>= 5.0)
46
+ ruby-progressbar
47
+ multi_json (1.14.1)
48
+ multipart-post (2.1.1)
49
+ nokogiri (1.10.9)
50
+ mini_portile2 (~> 2.4.0)
51
+ os (1.1.0)
52
+ public_suffix (4.0.5)
53
+ rake (12.3.3)
54
+ ruby-progressbar (1.10.1)
55
+ signet (0.14.0)
56
+ addressable (~> 2.3)
57
+ faraday (>= 0.17.3, < 2.0)
58
+ jwt (>= 1.5, < 3.0)
59
+ multi_json (~> 1.10)
60
+ thread_safe (0.3.6)
61
+ tzinfo (1.2.7)
62
+ thread_safe (~> 0.1)
63
+ zeitwerk (2.3.0)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ ad_localize!
70
+ bundler (>= 1.12.0, < 3.0.0)
71
+ byebug (~> 11.0)
72
+ diffy (~> 3.3)
73
+ minitest (~> 5.11)
74
+ minitest-reporters (~> 1.3)
75
+ rake (~> 12.3)
76
+
77
+ BUNDLED WITH
78
+ 2.1.4
@@ -0,0 +1,11 @@
1
+ build:
2
+ gem build ad_localize.gemspec
3
+
4
+ install: clean build
5
+ gem install ad_localize-*.gem
6
+
7
+ clean:
8
+ rm -f ad_localize-*.gem
9
+
10
+ tests:
11
+ bundle exec rake test
data/README.md CHANGED
@@ -29,15 +29,31 @@ $ gem install ad_localize
29
29
  $ bundle exec ad_localize -h
30
30
  ```
31
31
 
32
- * Export wording from a google drive spreadsheet, using the file key
32
+ * Export wording from a google spreadsheet, using the file key. Make sure to enable _Allow external access_ in sharing options.
33
33
  ```
34
34
  $ bundle exec ad_localize -k <your-spreadsheet-drive-key>
35
35
  ```
36
- * Export wording from a google drive spreadsheet, using the file key and specifying a sheet (useful when your file has multiple sheets)
36
+
37
+ * Export wording from a google spreadsheet, using the file key and specifying a sheet (useful when your file has multiple sheets)
37
38
  ```
38
39
  $ bundle exec ad_localize -k <your-spreadsheet-drive-key> -s <your-specific-sheet-id>
39
40
  ```
40
41
 
42
+ * Export wording from a private google spreadsheet using a Google Cloud Service Account.
43
+ 1. Create a GCloud Service Account:
44
+ - Go to [Google Cloud Console](https://console.cloud.google.com/)
45
+ - Either create a new project or use an existing one (when using Firebase, a GCloud project is created)
46
+ - Go to *IAM & Admin / Service Account* and create a new service account.
47
+ - Store the created `client-secret.json` (in a password manager for example)
48
+ 2. Enable Google Drive API for the projet
49
+ - Go to *API / Library* and enable the **Drive API** there.
50
+ 3. Add the service account to a spreadsheet.
51
+ - In *IAM & Admin / Service Account*, the service account's email is listed. Invite it to the spreadsheet to export.
52
+ ```
53
+ $ export GCLOUD_CLIENT_SECRET=$(cat <client-secret.json>)
54
+ $ bundle exec ad_localize -k <your-spreadsheet-drive-key> -a
55
+ ```
56
+
41
57
  * Only generate wording files for the specified platforms
42
58
  ```
43
59
  $ bundle exec ad_localize -o ios
@@ -62,10 +78,11 @@ $ bundle exec ad_localize -d
62
78
  | favorites | Mes favoris | My favorites |
63
79
  | from_to | du %1$@ au %2$@ | from %1$@ to %2$@ |
64
80
 
65
- - Any column after the `key` column will be considered as a locale column (except from the optional `comment columns)
81
+ - Any column after the `key` column will be considered as a locale column (except from the optional `comment columns)
66
82
  - Keys should be written in Android format : [a-z0-9_]+
67
83
  - Format specifiers must be numeroted if there are more than one in a translation string (eg: "%1$@ %2$@'s report").
68
84
 
85
+
69
86
  #### Comment columns
70
87
 
71
88
  In iOS (and only iOS) you can add a comment to a missing translation.
@@ -111,6 +128,8 @@ exports/
111
128
  └── fr.yml
112
129
  ```
113
130
 
131
+ NB: If you select only one platform, the wording files will directly be generated in the output path. The output path is the `exports` folder in the current directory by default and you can change it using the option `-o`.
132
+
114
133
  ## Plurals
115
134
 
116
135
  Plurals are supported for iOS and Android.
@@ -177,6 +196,41 @@ Every key that matches the following formats will be added to the `InfoPlist.str
177
196
 
178
197
  Source: https://developer.apple.com/documentation/bundleresources/information_property_list
179
198
 
199
+ ## Adaptive strings
200
+
201
+ _Only for iOS._
202
+
203
+ Syntax for adaptive keys in the CSV file:
204
+
205
+ key##{20}
206
+ key##{25}
207
+ key##{50}
208
+
209
+
210
+ Sample of iOS output in .stringsdict
211
+
212
+ ```xml
213
+ <?xml version="1.0" encoding="UTF-8"?>
214
+ <plist>
215
+ <dict>
216
+ <key>start_countdown</key>
217
+ <dict>
218
+ <key>NSStringVariableWidthRuleType</key>
219
+ <dict>
220
+ <key>20</key>
221
+ <string>Start</string>
222
+ <key>25</key>
223
+ <string>Start countdown</string>
224
+ <key>50</key>
225
+ <string>Start countdown</string>
226
+ </dict>
227
+ </dict>
228
+ </dict>
229
+ </plist>
230
+ ```
231
+
232
+ Source: https://developer.apple.com/documentation/foundation/nsstring/1413104-variantfittingpresentationwidth
233
+
180
234
  ## Development
181
235
 
182
236
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -19,14 +19,16 @@ Gem::Specification.new do |spec|
19
19
  'Nicolas Braun',
20
20
  'Corentin Huard',
21
21
  'Claire Peyron',
22
- 'Claire Dufetrelle'
22
+ 'Claire Dufetrelle',
23
+ 'Pierre Felgines',
24
+ 'Satyan Jacquens'
23
25
  ]
24
26
  spec.email = %w(joanna.vigne@fabernovel.com hugo.hache@fabernovel.com edouard.siegel@fabernovel.com)
25
27
 
26
28
  spec.summary = %q{AdLocalize helps with mobile and web applications wording}
27
29
  spec.description = %q{AdLocalize produces localization files from platform agnostic wording.
28
30
  Supported wording format : CSV. Supported export format: iOS, Android, JSON and YAML}
29
- spec.homepage = 'https://technologies.fabernovel.com'
31
+ spec.homepage = 'https://github.com/applidium/ad_localize'
30
32
 
31
33
  # Specify which files should be added to the gem when it is released.
32
34
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -42,10 +44,12 @@ Gem::Specification.new do |spec|
42
44
  spec.add_development_dependency 'minitest', '~> 5.11'
43
45
  spec.add_development_dependency 'byebug', '~> 11.0'
44
46
  spec.add_development_dependency 'minitest-reporters', '~> 1.3'
47
+ spec.add_development_dependency 'diffy', '~> 3.3'
45
48
 
46
- spec.add_dependency 'activesupport', '~> 4.2' ,'>= 4.2.10' # Fastlane does not support activesupport 5
47
- spec.add_dependency 'nokogiri', '~> 1.8', '>= 1.8.2'
49
+ spec.add_dependency 'activesupport', '>= 5.2', '< 7.0'
50
+ spec.add_dependency 'nokogiri', '~> 1.10'
48
51
  spec.add_dependency 'colorize', '~> 0.8'
52
+ spec.add_dependency 'googleauth', '~> 0.12'
49
53
 
50
- spec.required_ruby_version = '~> 2.3'
54
+ spec.required_ruby_version = '~> 2.3'
51
55
  end
@@ -8,6 +8,8 @@ require 'csv'
8
8
  require 'logger'
9
9
  require 'colorize'
10
10
  require 'open-uri'
11
+ require 'stringio'
12
+ require 'googleauth'
11
13
  require 'optparse'
12
14
  require 'nokogiri'
13
15
 
@@ -2,6 +2,7 @@ module AdLocalize
2
2
  module Constant
3
3
  SUPPORTED_PLATFORMS = %w(ios android yml json)
4
4
  PLURAL_KEY_SYMBOL = :plural
5
+ ADAPTIVE_KEY_SYMBOL = :adaptive
5
6
  SINGULAR_KEY_SYMBOL = :singular
6
7
  COMMENT_KEY_SYMBOL = :comment
7
8
  INFO_PLIST_KEY_SYMBOL = :info_plist
@@ -8,13 +8,20 @@ module AdLocalize
8
8
  end
9
9
 
10
10
  # Returns the downloaded file name (it is located in the current directory)
11
- def download_from_drive(key, sheet)
12
- LOGGER.log(:info, :black, "Downloading file from google drive...")
11
+ def download_from_drive(key, sheet, use_service_account=false)
12
+ LOGGER.log(:info, :green, "Downloading file from google drive...")
13
13
  download_string_path = "./#{key}.csv"
14
14
  begin
15
15
  File.open(download_string_path, "wb") do |saved_file|
16
+ download_url = drive_download_url(key, sheet)
17
+ headers = {}
18
+ if use_service_account
19
+ LOGGER.log(:debug, :green, "Using a service account...")
20
+ token = service_account_access_token()
21
+ headers["Authorization"] = "#{token["token_type"]} #{token["access_token"]}"
22
+ end
16
23
  # the following "open" is provided by open-uri
17
- open(drive_download_url(key, sheet), "rb") do |read_file|
24
+ open(download_url, "rb", headers) do |read_file|
18
25
  saved_file.write(read_file.read)
19
26
  end
20
27
  File.basename saved_file
@@ -42,6 +49,16 @@ module AdLocalize
42
49
  query_id = sheet ? "gid=#{sheet}" : "id=#{key}"
43
50
  "https://docs.google.com/spreadsheets/d/#{key}/export?format=csv&#{query_id}"
44
51
  end
52
+ private
53
+ def service_account_access_token()
54
+ scope = "https://www.googleapis.com/auth/drive.readonly"
55
+ json_key_io = StringIO.new ENV["GCLOUD_CLIENT_SECRET"]
56
+ authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
57
+ json_key_io: json_key_io,
58
+ scope: scope
59
+ )
60
+ authorizer.fetch_access_token!
61
+ end
45
62
  end
46
63
  end
47
64
  end
@@ -1,7 +1,8 @@
1
1
  module AdLocalize
2
2
  class CsvParser
3
3
  CSV_WORDING_KEYS_COLUMN = "key"
4
- PLURAL_KEY_REGEXP = /\#\#\{(\w+)\}/
4
+ PLURAL_KEY_REGEXP = /\#\#\{([A-Za-z]+)\}/
5
+ ADAPTIVE_KEY_REGEXP = /\#\#\{(\d+)\}/
5
6
  INFO_PLIST_KEY_REGEXP = /(NS.+UsageDescription)|(CF.+Name)/ # see https://developer.apple.com/documentation/bundleresources/information_property_list
6
7
 
7
8
  attr_accessor :locales
@@ -57,7 +58,7 @@ module AdLocalize
57
58
  if locales.empty?
58
59
  raise 'NO DETECTED LOCALES'
59
60
  else
60
- LOGGER.log(:debug, :black, "DETECTED LOCALES : #{locales.join(', ')}")
61
+ LOGGER.log(:debug, :green, "DETECTED LOCALES : #{locales.join(', ')}")
61
62
  end
62
63
  end
63
64
 
@@ -78,6 +79,11 @@ module AdLocalize
78
79
  "[#{locale.upcase}] Plural key ---> plural_identifier : #{key_infos.dig(:plural_identifier)}, key : #{current_key}",
79
80
  "[#{locale.upcase}] Missing translation for #{current_key} (#{key_infos.dig(:plural_identifier)})")
80
81
  value = { key_infos.dig(:plural_identifier) => row[locale] || default_wording(locale, "#{current_key} (#{key_infos.dig(:plural_identifier)})") }
82
+ elsif key_infos.dig(:numeral_key) == Constant::ADAPTIVE_KEY_SYMBOL
83
+ trace_wording(row[locale],
84
+ "[#{locale.upcase}] Adaptive key ---> adaptive_identifier : #{key_infos.dig(:adaptive_identifier)}, key : #{current_key}",
85
+ "[#{locale.upcase}] Missing translation for #{current_key} (#{key_infos.dig(:adaptive_identifier)})")
86
+ value = { key_infos.dig(:adaptive_identifier) => row[locale] || default_wording(locale, "#{current_key} (#{key_infos.dig(:adaptive_identifier)})") }
81
87
  elsif key_infos.dig(:numeral_key) == Constant::INFO_PLIST_KEY_SYMBOL
82
88
  trace_wording(row[locale], "[#{locale.upcase}] Info.plist key ---> #{current_key}", "[#{locale.upcase}] Missing translation for #{current_key}")
83
89
  value = row[locale] || default_wording(locale, current_key)
@@ -93,27 +99,36 @@ module AdLocalize
93
99
  end
94
100
 
95
101
  def parse_key(row)
96
- key = row.field(CSV_WORDING_KEYS_COLUMN)
102
+ key = row.field(CSV_WORDING_KEYS_COLUMN).strip
97
103
  plural_prefix = key.match(PLURAL_KEY_REGEXP)
98
104
  plural_identifier = nil
99
105
  invalid_plural = false
100
106
 
101
- if plural_prefix.nil?
102
- if key.match(INFO_PLIST_KEY_REGEXP).nil?
103
- numeral_key = Constant::SINGULAR_KEY_SYMBOL
104
- else
105
- numeral_key = Constant::INFO_PLIST_KEY_SYMBOL
106
- end
107
- else
107
+ adaptive_prefix = key.match(ADAPTIVE_KEY_REGEXP)
108
+ adaptive_identifier = nil
109
+ invalid_adaptive = false
110
+
111
+ if plural_prefix != nil
108
112
  numeral_key = Constant::PLURAL_KEY_SYMBOL
109
113
  key = plural_prefix.pre_match
110
114
  plural_identifier = plural_prefix.captures&.first
111
115
  LOGGER.log(:debug, :red, "Invalid key #{key}") if key.nil?
112
116
  LOGGER.log(:debug, :red, "Empty plural prefix!") if plural_identifier.nil?
113
117
  invalid_plural = plural_identifier.nil?
118
+ elsif adaptive_prefix != nil
119
+ numeral_key = Constant::ADAPTIVE_KEY_SYMBOL
120
+ key = adaptive_prefix.pre_match
121
+ adaptive_identifier = adaptive_prefix.captures&.first
122
+ LOGGER.log(:debug, :red, "Invalid key #{key}") if key.nil?
123
+ LOGGER.log(:debug, :red, "Empty adaptive prefix!") if adaptive_identifier.nil?
124
+ invalid_adaptive = adaptive_identifier.nil?
125
+ elsif key.match(INFO_PLIST_KEY_REGEXP) != nil
126
+ numeral_key = Constant::INFO_PLIST_KEY_SYMBOL
127
+ else
128
+ numeral_key = Constant::SINGULAR_KEY_SYMBOL
114
129
  end
115
130
 
116
- (key.nil? or invalid_plural) ? nil : { key: key.to_sym, numeral_key: numeral_key, plural_identifier: plural_identifier&.to_sym }
131
+ (key.nil? or invalid_plural or invalid_adaptive) ? nil : { key: key.to_sym, numeral_key: numeral_key, plural_identifier: plural_identifier&.to_sym, adaptive_identifier: adaptive_identifier&.to_sym }
117
132
  end
118
133
 
119
134
  def default_wording(locale, key)
@@ -122,7 +137,7 @@ module AdLocalize
122
137
 
123
138
  def trace_wording(wording, present_message, missing_message)
124
139
  if wording
125
- LOGGER.log(:debug, :black, present_message)
140
+ LOGGER.log(:debug, :green, present_message)
126
141
  else
127
142
  LOGGER.log(:debug, :yellow, missing_message)
128
143
  end
@@ -20,11 +20,15 @@ module AdLocalize
20
20
  end
21
21
  opts.on("-k", "--drive-key #{GOOGLE_DRIVE_DOCUMENT_ID.dig(:length)}_characters", String, "Use google drive spreadsheets") do |key|
22
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
23
+ raise ArgumentError.new("Invalid google drive spreadsheet key \"#{key}\"") unless is_valid_drive_key
24
+ args[:drive_key] = key
24
25
  end
25
26
  opts.on("-s", "--drive-sheet SHEET_ID", String, "Use a specific sheet id for Google Drive spreadsheets with several sheets") do |value|
26
27
  args[:sheet_id] = value
27
28
  end
29
+ opts.on("-a", "--use-service-account", "Use a Google Cloud Service Account to access the file. An GCLOUD_CLIENT_SECRET environment variable containting the client_secret.json content is needed.") do
30
+ args[:use_service_account] = true
31
+ end
28
32
  opts.on("-o", "--only platform1,platform2", Array, "Only generate localisation files for the specified platforms. Supported platforms : #{Constant::SUPPORTED_PLATFORMS.join(', ')}") do |platforms|
29
33
  args[:only] = filter_option_args("-o", platforms) { |platform| !!Constant::SUPPORTED_PLATFORMS.index(platform) }
30
34
  end
@@ -44,6 +48,7 @@ module AdLocalize
44
48
  LOGGER.log(:error, :red, "Missing argument for option #{e.args.join(',')}")
45
49
  rescue ArgumentError => e
46
50
  LOGGER.log(:error, :red, e.message)
51
+ raise e
47
52
  end
48
53
  args
49
54
  end
@@ -57,7 +57,7 @@ module AdLocalize::Platform
57
57
  add_wording_text_to_xml(plural_text, xml)
58
58
  }
59
59
  end
60
- }
60
+ } unless plural_hash.values.all? &:empty?
61
61
  end
62
62
 
63
63
  def add_wording_text_to_xml(wording_text, xml)
@@ -67,4 +67,4 @@ module AdLocalize::Platform
67
67
  )
68
68
  end
69
69
  end
70
- end
70
+ end
@@ -6,10 +6,16 @@ module AdLocalize::Platform
6
6
 
7
7
  def export(locale, data, export_extension = nil, substitution_format = nil)
8
8
  create_locale_dir(locale)
9
- [AdLocalize::Constant::PLURAL_KEY_SYMBOL, AdLocalize::Constant::SINGULAR_KEY_SYMBOL, AdLocalize::Constant::INFO_PLIST_KEY_SYMBOL].each do |numeral_key|
9
+ all_symbols = [
10
+ AdLocalize::Constant::PLURAL_KEY_SYMBOL,
11
+ AdLocalize::Constant::ADAPTIVE_KEY_SYMBOL,
12
+ AdLocalize::Constant::SINGULAR_KEY_SYMBOL,
13
+ AdLocalize::Constant::INFO_PLIST_KEY_SYMBOL
14
+ ]
15
+ all_symbols.each do |numeral_key|
10
16
  numeral_data = data.select {|key, wording| wording.dig(locale.to_sym)&.key? numeral_key}
11
17
  if numeral_data.empty?
12
- AdLocalize::LOGGER.log(:info, :black, "[#{locale.upcase}] no #{numeral_key.to_s} keys were found to generate the file")
18
+ AdLocalize::LOGGER.log(:info, :green, "[#{locale.upcase}] no #{numeral_key.to_s} keys were found to generate the file")
13
19
  else
14
20
  send("write_#{numeral_key}", locale, numeral_data)
15
21
  end
@@ -36,53 +42,102 @@ module AdLocalize::Platform
36
42
  )
37
43
  end
38
44
 
45
+ def write_plural(locale, plurals)
46
+ locale = locale.to_sym
47
+
48
+ append_to_localizable_plist(locale) do |xml|
49
+ plurals.each do |wording_key, translations|
50
+ xml.key wording_key
51
+ xml.dict {
52
+ xml.key "NSStringLocalizedFormatKey"
53
+ xml.string "%\#@key@"
54
+ xml.key "key"
55
+ xml.dict {
56
+ xml.key "NSStringFormatSpecTypeKey"
57
+ xml.string "NSStringPluralRuleType"
58
+ xml.key "NSStringFormatValueTypeKey"
59
+ xml.string "d"
60
+ translations[locale][AdLocalize::Constant::PLURAL_KEY_SYMBOL].each do |wording_type, wording_value|
61
+ xml.key wording_type
62
+ xml.string wording_value
63
+ end
64
+ }
65
+ }
66
+ end
67
+ end
68
+ AdLocalize::LOGGER.log(:debug, :green, "iOS plural [#{locale}] ---> DONE!")
69
+ end
70
+
71
+ def write_adaptive(locale, adaptives)
72
+ locale = locale.to_sym
73
+
74
+ append_to_localizable_plist(locale) do |xml|
75
+ adaptives.each do |wording_key, translations|
76
+ xml.key wording_key
77
+ xml.dict {
78
+ xml.key "NSStringVariableWidthRuleType"
79
+ xml.dict {
80
+ translations[locale][AdLocalize::Constant::ADAPTIVE_KEY_SYMBOL].each do |wording_type, wording_value|
81
+ xml.key wording_type
82
+ xml.string wording_value
83
+ end
84
+ }
85
+ }
86
+ end
87
+ end
88
+ AdLocalize::LOGGER.log(:debug, :black, "iOS adaptive [#{locale}] ---> DONE!")
89
+ end
90
+
91
+ private
92
+
93
+ def escape_quotes_if_needed(value)
94
+ value.gsub(/(?<!\\)\"/, "\\\"") # match " unless there is a \ before
95
+ end
96
+
39
97
  def write_localizable(locale:, singulars:, wording_type:, filename:)
40
98
  locale = locale.to_sym
41
99
 
42
100
  singulars.each do |key, locales_wording|
43
101
  value = locales_wording.dig(locale, wording_type)
102
+ value = escape_quotes_if_needed(value)
44
103
  comment = locales_wording.dig(locale, AdLocalize::Constant::COMMENT_KEY_SYMBOL)
45
104
  export_dir(locale).join(filename).open("a") do |file|
46
- line = "\"#{key}\" = \"#{value}\";"
105
+ line = ""
106
+ if wording_type == AdLocalize::Constant::INFO_PLIST_KEY_SYMBOL
107
+ line = %(#{key} = "#{value}";)
108
+ else
109
+ line = %("#{key}" = "#{value}";)
110
+ end
47
111
  line << " // #{comment}" unless comment.nil?
48
112
  line << "\n"
49
113
  file.puts line
50
114
  end
51
115
  end
52
- AdLocalize::LOGGER.log(:debug, :black, "iOS #{wording_type} [#{locale}] ---> DONE!")
116
+ AdLocalize::LOGGER.log(:debug, :green, "iOS #{wording_type} [#{locale}] ---> DONE!")
53
117
  end
54
118
 
55
- def write_plural(locale, plurals)
56
- locale = locale.to_sym
57
-
58
- xml_doc = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
59
- xml.plist {
60
- xml.dict {
61
- plurals.each do |wording_key, translations|
62
- xml.key wording_key
63
- xml.dict {
64
- xml.key "NSStringLocalizedFormatKey"
65
- xml.string "%\#@key@"
66
- xml.key "key"
67
- xml.dict {
68
- xml.key "NSStringFormatSpecTypeKey"
69
- xml.string "NSStringPluralRuleType"
70
- xml.key "NSStringFormatValueTypeKey"
71
- xml.string "d"
72
- translations[locale][AdLocalize::Constant::PLURAL_KEY_SYMBOL].each do |wording_type, wording_value|
73
- xml.key wording_type
74
- xml.string wording_value
75
- end
76
- }
77
- }
78
- end
119
+ def append_to_localizable_plist(locale)
120
+ filename = export_dir(locale).join(AdLocalize::Constant::IOS_PLURAL_EXPORT_FILENAME)
121
+ xml_doc = nil
122
+ if File.exist?(filename)
123
+ xml_content = Nokogiri::XML(open(filename)) do |config|
124
+ config.default_xml.noblanks # minimify xml
125
+ end
126
+ xml_doc = Nokogiri::XML::Builder.with(xml_content.css('plist>dict').first) do |xml|
127
+ yield(xml)
128
+ end
129
+ else
130
+ xml_doc = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
131
+ xml.plist {
132
+ xml.dict {
133
+ yield(xml)
134
+ }
79
135
  }
80
- }
136
+ end
81
137
  end
82
- export_dir(locale).join(AdLocalize::Constant::IOS_PLURAL_EXPORT_FILENAME).open("w") do |file|
138
+ filename.open("w") do |file|
83
139
  file.puts xml_doc.to_xml(indent: 4)
84
140
  end
85
- AdLocalize::LOGGER.log(:debug, :black, "iOS plural [#{locale}] ---> DONE!")
86
141
  end
87
142
  end
88
- end
143
+ end
@@ -4,15 +4,14 @@ module AdLocalize::Platform
4
4
  :json
5
5
  end
6
6
 
7
- def export(locale, data, export_extension = "json", substitution_format = "angular")
8
- super(locale, data, export_extension, substitution_format) do |json_data, file|
7
+ def export(locale, data, export_extension = "json", substitution_format = "react")
8
+ locale = locale.to_sym
9
+ json_data = build_platform_data_splitting_on_dots(locale, data)
10
+ platform_dir.join("#{locale.to_s}.#{export_extension}").open("w") do |file|
9
11
  file.puts json_data.to_json
10
12
  end
11
- end
12
13
 
13
- protected
14
- def ios_converter(value)
15
- value.gsub(/(%(?!)?(\d+\$)?[@isd])/, "VARIABLE_TO_SET")
14
+ AdLocalize::LOGGER.log(:debug, :green, "JSON [#{locale}] ---> DONE!")
16
15
  end
17
16
  end
18
- end
17
+ end
@@ -5,12 +5,13 @@ module AdLocalize::Platform
5
5
  OPTIONS = {output_path: ".."}
6
6
 
7
7
  attr_accessor :platform_dir
8
- attr_reader :default_locale, :output_path
8
+ attr_reader :default_locale, :output_path, :add_intermediate_platform_dir
9
9
 
10
- def initialize(default_locale, output_path)
10
+ def initialize(default_locale, output_path, add_intermediate_platform_dir)
11
11
  @default_locale = default_locale.to_sym
12
12
  @output_path = output_path # Must be set before platform_dir
13
- @platform_dir = export_base_directory.join(platform.to_s)
13
+ @platform_dir = add_intermediate_platform_dir ? export_base_directory.join(platform.to_s) : export_base_directory
14
+ @add_intermediate_platform_dir = add_intermediate_platform_dir
14
15
  create_platform_dir
15
16
  end
16
17
 
@@ -39,7 +40,7 @@ module AdLocalize::Platform
39
40
  yield(formatted_data, file)
40
41
  end
41
42
 
42
- AdLocalize::LOGGER.log(:debug, :black, "#{platform.to_s.upcase} [#{locale}] ---> DONE!")
43
+ AdLocalize::LOGGER.log(:debug, :green, "#{platform.to_s.upcase} [#{locale}] ---> DONE!")
43
44
  end
44
45
 
45
46
  protected
@@ -52,7 +53,7 @@ module AdLocalize::Platform
52
53
  end
53
54
 
54
55
  def create_platform_dir
55
- if platform_dir.directory?
56
+ if platform_dir.directory? && add_intermediate_platform_dir
56
57
  FileUtils.rm_rf("#{platform_dir}/.", secure: true)
57
58
  else
58
59
  platform_dir.mkpath
@@ -72,5 +73,37 @@ module AdLocalize::Platform
72
73
  export_dir(locale).mkdir
73
74
  end
74
75
  end
76
+
77
+ def build_platform_data_splitting_on_dots(locale, data)
78
+ data.each_with_object({}) do |(key, wording), hash_acc|
79
+ hash_acc[locale.to_s] = {} unless hash_acc.key? locale.to_s
80
+ keys = key.to_s.split('.')
81
+ hash = hash_acc[locale.to_s]
82
+ keys.each_with_index do |inner_key, index|
83
+ if index === keys.count - 1
84
+ fill_hash(wording, locale, hash, inner_key)
85
+ else
86
+ if hash[inner_key].nil?
87
+ hash[inner_key] = {}
88
+ end
89
+ hash = hash[inner_key]
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def fill_hash(wording, locale, hash, key)
96
+ if wording.dig(locale)&.key? :singular
97
+ value = wording.dig(locale, :singular)
98
+ hash[key.to_s] = value
99
+ end
100
+ if wording.dig(locale)&.key? :plural
101
+ hash[key.to_s] = {}
102
+ wording.dig(locale, :plural).each do |plural_type, plural_text|
103
+ value = plural_text
104
+ hash[key.to_s][plural_type.to_s] = value
105
+ end
106
+ end
107
+ end
75
108
  end
76
109
  end
@@ -5,14 +5,15 @@ module AdLocalize::Platform
5
5
  end
6
6
 
7
7
  def export(locale, data, export_extension = "yml", substitution_format = "yml")
8
- super(locale, data, export_extension, substitution_format) do |yml_data, file|
8
+ locale = locale.to_sym
9
+ yml_data = build_platform_data_splitting_on_dots(locale, data)
10
+
11
+ platform_dir.join("#{locale.to_s}.#{export_extension}").open("w") do |file|
9
12
  file.puts yml_data.to_yaml
10
13
  end
11
- end
12
14
 
13
- protected
14
- def ios_converter(value)
15
- value.gsub(/(%(?!)?(\d+\$)?[@isd])/, "VARIABLE_TO_SET")
15
+ AdLocalize::LOGGER.log(:debug, :green, "YAML [#{locale}] ---> DONE!")
16
16
  end
17
+
17
18
  end
18
19
  end
@@ -7,7 +7,7 @@ module AdLocalize
7
7
  end
8
8
 
9
9
  def run(args = ARGV)
10
- LOGGER.log(:info, :black, "OPTIONS : #{options}")
10
+ LOGGER.log(:info, :green, "OPTIONS : #{options}")
11
11
  input_files = (args + [options.dig(:drive_key)]).compact # drive_key can be nil
12
12
  if input_files.length.zero?
13
13
  LOGGER.log(:error, :red, "No CSV to parse. Use option -h to see how to use this script")
@@ -15,26 +15,36 @@ module AdLocalize
15
15
  file_to_parse = args.first
16
16
  LOGGER.log(:warn, :yellow, "Only one CSV can be treated - the priority goes to #{file_to_parse}") if input_files.length > 1
17
17
  if args.empty?
18
- options[:drive_file] = CsvFileManager.download_from_drive(options.dig(:drive_key), options.dig(:sheet_id))
18
+ options[:drive_file] = CsvFileManager.download_from_drive(options.dig(:drive_key), options.dig(:sheet_id), options.dig(:use_service_account))
19
19
  file_to_parse = options.dig(:drive_file)
20
20
  end
21
- CsvFileManager.csv?(file_to_parse) ? export(file_to_parse) : LOGGER.log(:error, :red, "#{file_to_parse} is not a csv")
21
+ if CsvFileManager.csv?(file_to_parse)
22
+ export(file_to_parse)
23
+ else
24
+ LOGGER.log(:error, :red, "#{file_to_parse} is not a csv. Make sure to enable \"Allow external access\" in sharing options.")
25
+ end
22
26
  CsvFileManager.delete_drive_file(options[:drive_file]) if options[:drive_file]
23
27
  end
24
28
  end
25
29
 
26
30
  private
31
+
27
32
  def export(file)
28
- LOGGER.log(:info, :black, "********* PARSING #{file} *********")
29
- LOGGER.log(:info, :black, "Extracting data from file...")
33
+ LOGGER.log(:info, :green, "********* PARSING #{file} *********")
34
+ LOGGER.log(:info, :green, "Extracting data from file...")
30
35
  parser = CsvParser.new
31
36
  data = parser.extract_data(file)
32
37
  if data.empty?
33
38
  LOGGER.log(:error, :red, "No data were found in the file - check if there is a key column in the file")
34
39
  else
35
40
  export_platforms = options.dig(:only) || Constant::SUPPORTED_PLATFORMS
41
+ add_intermediate_platform_dir = export_platforms.length > 1
36
42
  export_platforms.each do |platform|
37
- platform_formatter = "AdLocalize::Platform::#{platform.to_s.camelize}Formatter".constantize.new(parser.locales.first, options.dig(:output_path))
43
+ platform_formatter = "AdLocalize::Platform::#{platform.to_s.camelize}Formatter".constantize.new(
44
+ parser.locales.first,
45
+ options.dig(:output_path),
46
+ add_intermediate_platform_dir
47
+ )
38
48
  parser.locales.each do |locale|
39
49
  platform_formatter.export(locale, data)
40
50
  end
@@ -42,4 +52,4 @@ module AdLocalize
42
52
  end
43
53
  end
44
54
  end
45
- end
55
+ end
@@ -1,3 +1,3 @@
1
1
  module AdLocalize
2
- VERSION = "3.1.0"
2
+ VERSION = "3.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ad_localize
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Siegel
@@ -15,6 +15,8 @@ authors:
15
15
  - Corentin Huard
16
16
  - Claire Peyron
17
17
  - Claire Dufetrelle
18
+ - Pierre Felgines
19
+ - Satyan Jacquens
18
20
  autorequire:
19
21
  bindir: exe
20
22
  cert_chain: []
@@ -97,45 +99,53 @@ dependencies:
97
99
  - !ruby/object:Gem::Version
98
100
  version: '1.3'
99
101
  - !ruby/object:Gem::Dependency
100
- name: activesupport
102
+ name: diffy
101
103
  requirement: !ruby/object:Gem::Requirement
102
104
  requirements:
103
105
  - - "~>"
104
106
  - !ruby/object:Gem::Version
105
- version: '4.2'
107
+ version: '3.3'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '3.3'
115
+ - !ruby/object:Gem::Dependency
116
+ name: activesupport
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
106
119
  - - ">="
107
120
  - !ruby/object:Gem::Version
108
- version: 4.2.10
121
+ version: '5.2'
122
+ - - "<"
123
+ - !ruby/object:Gem::Version
124
+ version: '7.0'
109
125
  type: :runtime
110
126
  prerelease: false
111
127
  version_requirements: !ruby/object:Gem::Requirement
112
128
  requirements:
113
- - - "~>"
114
- - !ruby/object:Gem::Version
115
- version: '4.2'
116
129
  - - ">="
117
130
  - !ruby/object:Gem::Version
118
- version: 4.2.10
131
+ version: '5.2'
132
+ - - "<"
133
+ - !ruby/object:Gem::Version
134
+ version: '7.0'
119
135
  - !ruby/object:Gem::Dependency
120
136
  name: nokogiri
121
137
  requirement: !ruby/object:Gem::Requirement
122
138
  requirements:
123
139
  - - "~>"
124
140
  - !ruby/object:Gem::Version
125
- version: '1.8'
126
- - - ">="
127
- - !ruby/object:Gem::Version
128
- version: 1.8.2
141
+ version: '1.10'
129
142
  type: :runtime
130
143
  prerelease: false
131
144
  version_requirements: !ruby/object:Gem::Requirement
132
145
  requirements:
133
146
  - - "~>"
134
147
  - !ruby/object:Gem::Version
135
- version: '1.8'
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: 1.8.2
148
+ version: '1.10'
139
149
  - !ruby/object:Gem::Dependency
140
150
  name: colorize
141
151
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +160,20 @@ dependencies:
150
160
  - - "~>"
151
161
  - !ruby/object:Gem::Version
152
162
  version: '0.8'
163
+ - !ruby/object:Gem::Dependency
164
+ name: googleauth
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: '0.12'
170
+ type: :runtime
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - "~>"
175
+ - !ruby/object:Gem::Version
176
+ version: '0.12'
153
177
  description: |-
154
178
  AdLocalize produces localization files from platform agnostic wording.
155
179
  Supported wording format : CSV. Supported export format: iOS, Android, JSON and YAML
@@ -162,12 +186,15 @@ executables:
162
186
  extensions: []
163
187
  extra_rdoc_files: []
164
188
  files:
189
+ - ".github/workflows/ruby.yml"
165
190
  - ".gitignore"
166
191
  - ".travis.yml"
167
192
  - CHANGELOG.md
168
193
  - CODE_OF_CONDUCT.md
169
194
  - Gemfile
195
+ - Gemfile.lock
170
196
  - LICENSE.txt
197
+ - Makefile
171
198
  - README.md
172
199
  - Rakefile
173
200
  - ad_localize.gemspec
@@ -188,7 +215,7 @@ files:
188
215
  - lib/ad_localize/platform/yml_formatter.rb
189
216
  - lib/ad_localize/runner.rb
190
217
  - lib/ad_localize/version.rb
191
- homepage: https://technologies.fabernovel.com
218
+ homepage: https://github.com/applidium/ad_localize
192
219
  licenses:
193
220
  - MIT
194
221
  metadata: {}
@@ -207,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
234
  - !ruby/object:Gem::Version
208
235
  version: '0'
209
236
  requirements: []
210
- rubygems_version: 3.0.2
237
+ rubygems_version: 3.0.3
211
238
  signing_key:
212
239
  specification_version: 4
213
240
  summary: AdLocalize helps with mobile and web applications wording