ad_localize 6.3.0 → 6.4.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 +4 -4
- data/.github/workflows/ruby.yml +7 -3
- data/.gitignore +2 -0
- data/CHANGELOG.md +17 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -45
- data/README.md +46 -2
- data/ad_localize.gemspec +4 -2
- data/lib/ad_localize/cli.rb +1 -1
- data/lib/ad_localize/interactors/download_spreadsheets.rb +16 -0
- data/lib/ad_localize/interactors/merge_wordings.rb +2 -1
- data/lib/ad_localize/mappers/options_to_export_request.rb +1 -0
- data/lib/ad_localize/option_handler.rb +19 -6
- data/lib/ad_localize/repositories/office_repository.rb +114 -0
- data/lib/ad_localize/requests/export_request.rb +12 -0
- data/lib/ad_localize/version.rb +1 -1
- data/lib/ad_localize.rb +6 -0
- data/mise.toml +2 -0
- data/scripts/download_with_navigator +136 -0
- metadata +42 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fe1c4ef7a0a7be9e6dc5396baac0d71f6fd2bfc4cba1cb076abba1200714dc3
|
|
4
|
+
data.tar.gz: 0ced5f8eb06b1edc0699bd9f976a31f51499397bd49e8380babfca7bb20b312a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3c982c48934e519fc58f739b73f4a36424e018f2c09ba30a414432990a0db3ec8a3fe057f5adfbbdf785dec348b4b416b8db23019bffd72fbf7613be758ffe7
|
|
7
|
+
data.tar.gz: 915907946b8f8c7fe8c596e6810c7d72acdbee3cdfb2bbe5d8462083cd4e3636c125bfd6b379f4285e8d12df9da918d185379b20356c82378204ea4b5b4e6bd8
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -7,7 +7,7 @@ jobs:
|
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
strategy:
|
|
9
9
|
matrix:
|
|
10
|
-
ruby: [
|
|
10
|
+
ruby: ["3.2.0", "3.3.5", "3.4.10"]
|
|
11
11
|
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@v2
|
|
@@ -23,11 +23,15 @@ jobs:
|
|
|
23
23
|
- name: Run tests
|
|
24
24
|
run: bundle exec rake test
|
|
25
25
|
- run: 'echo "$GOOGLE_APPLICATION_CREDENTIALS" > account_secret.json'
|
|
26
|
+
# Disabled: only needed by the private google sheet tests below
|
|
27
|
+
if: false
|
|
26
28
|
shell: bash
|
|
27
29
|
env:
|
|
28
30
|
GOOGLE_APPLICATION_CREDENTIALS: ${{secrets.GOOGLE_APPLICATION_CREDENTIALS}}
|
|
29
31
|
- name: Run private google sheet tests
|
|
30
|
-
|
|
32
|
+
# Disabled: the private google sheet used by these tests is missing
|
|
33
|
+
# if: matrix.ruby == '3.3.5' # Limit execution on latest ruby version to prevent reaching rate limits
|
|
34
|
+
if: false
|
|
31
35
|
run: bundle exec rake test_private_google_sheet
|
|
32
36
|
env:
|
|
33
|
-
GOOGLE_APPLICATION_CREDENTIALS:
|
|
37
|
+
GOOGLE_APPLICATION_CREDENTIALS: "account_secret.json"
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## [6.4.0] - 2026-10-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add `--excel-file` option to support XLSX file format in addition of CSV files.
|
|
15
|
+
- Add `scripts/download_with_navigator`, a temporary workaround script that downloads a file through the user browser session (`--source sharepoint` turns a SharePoint share link into a download link)
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- Drop support for ruby 3.1
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fix plurals and adaptive wordings emptied when merging several sheets or csv files with the default `keep` merge policy
|
|
24
|
+
- Fix the key appended to the last value of a Google Sheet export without trailing newline, by requiring csv >= 3.2.7
|
|
25
|
+
|
|
10
26
|
## [6.3.0] - 2024-11-04
|
|
11
27
|
|
|
12
28
|
### Removed
|
|
13
29
|
|
|
14
|
-
- Drop support for
|
|
30
|
+
- Drop support for ruby <3.1
|
|
15
31
|
|
|
16
32
|
## [6.2.0] - 2023-12-12
|
|
17
33
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,102 +1,114 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
ad_localize (6.
|
|
4
|
+
ad_localize (6.4.0)
|
|
5
5
|
activesupport (>= 6.1.3.2, < 8.0)
|
|
6
6
|
colorize (~> 1.0)
|
|
7
|
+
csv (~> 3.2, >= 3.2.7)
|
|
7
8
|
google-apis-drive_v3 (~> 0.33.0)
|
|
8
9
|
google-apis-sheets_v4 (~> 0.9)
|
|
10
|
+
rubyXL (~> 3.4)
|
|
9
11
|
|
|
10
12
|
GEM
|
|
11
13
|
remote: https://rubygems.org/
|
|
12
14
|
specs:
|
|
13
|
-
activesupport (7.2.
|
|
15
|
+
activesupport (7.2.4)
|
|
14
16
|
base64
|
|
15
17
|
benchmark (>= 0.3)
|
|
16
18
|
bigdecimal
|
|
17
19
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
18
|
-
connection_pool (>= 2.2.5)
|
|
20
|
+
connection_pool (>= 2.2.5, < 3)
|
|
19
21
|
drb
|
|
20
22
|
i18n (>= 1.6, < 2)
|
|
21
23
|
logger (>= 1.4.2)
|
|
22
|
-
minitest (>= 5.1)
|
|
24
|
+
minitest (>= 5.1, < 6)
|
|
23
25
|
securerandom (>= 0.3)
|
|
24
26
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
25
|
-
addressable (2.
|
|
26
|
-
public_suffix (>= 2.0.2, <
|
|
27
|
+
addressable (2.9.0)
|
|
28
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
27
29
|
ansi (1.5.0)
|
|
28
30
|
ast (2.4.2)
|
|
29
|
-
base64 (0.
|
|
30
|
-
benchmark (0.
|
|
31
|
-
bigdecimal (
|
|
31
|
+
base64 (0.3.0)
|
|
32
|
+
benchmark (0.5.0)
|
|
33
|
+
bigdecimal (4.1.3)
|
|
32
34
|
builder (3.3.0)
|
|
33
35
|
colorize (1.1.0)
|
|
34
|
-
concurrent-ruby (1.3.
|
|
35
|
-
connection_pool (2.
|
|
36
|
+
concurrent-ruby (1.3.8)
|
|
37
|
+
connection_pool (2.5.5)
|
|
38
|
+
csv (3.3.6)
|
|
36
39
|
debug (1.9.2)
|
|
37
40
|
irb (~> 1.10)
|
|
38
41
|
reline (>= 0.3.8)
|
|
39
42
|
declarative (0.0.20)
|
|
40
43
|
diffy (3.4.3)
|
|
41
|
-
drb (2.2.
|
|
42
|
-
faraday (2.
|
|
43
|
-
faraday-net_http (>= 2.0, < 3.
|
|
44
|
+
drb (2.2.3)
|
|
45
|
+
faraday (2.14.4)
|
|
46
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
44
47
|
json
|
|
45
48
|
logger
|
|
46
|
-
faraday-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
faraday-follow_redirects (0.5.0)
|
|
50
|
+
faraday (>= 1, < 3)
|
|
51
|
+
faraday-net_http (3.4.4)
|
|
52
|
+
net-http (~> 0.5)
|
|
53
|
+
google-apis-core (1.2.5)
|
|
54
|
+
addressable (~> 2.9)
|
|
55
|
+
faraday (~> 2.13)
|
|
56
|
+
faraday-follow_redirects (~> 0.3)
|
|
57
|
+
googleauth (~> 1.14)
|
|
58
|
+
mini_mime (~> 1.1)
|
|
59
|
+
multi_json (~> 1.11)
|
|
54
60
|
representable (~> 3.0)
|
|
55
|
-
retriable (>=
|
|
61
|
+
retriable (>= 3.1, < 5.0)
|
|
56
62
|
google-apis-drive_v3 (0.33.0)
|
|
57
63
|
google-apis-core (>= 0.9.1, < 2.a)
|
|
58
|
-
google-apis-sheets_v4 (0.
|
|
64
|
+
google-apis-sheets_v4 (0.48.0)
|
|
59
65
|
google-apis-core (>= 0.15.0, < 2.a)
|
|
60
|
-
google-cloud-env (2.
|
|
66
|
+
google-cloud-env (2.4.0)
|
|
67
|
+
base64 (~> 0.2)
|
|
61
68
|
faraday (>= 1.0, < 3.a)
|
|
62
|
-
|
|
69
|
+
google-logging-utils (0.2.0)
|
|
70
|
+
googleauth (1.17.4)
|
|
63
71
|
faraday (>= 1.0, < 3.a)
|
|
64
|
-
google-cloud-env (~> 2.
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
google-cloud-env (~> 2.2)
|
|
73
|
+
google-logging-utils (~> 0.1)
|
|
74
|
+
jwt (>= 1.4, < 4.0)
|
|
67
75
|
os (>= 0.9, < 2.0)
|
|
76
|
+
pstore (~> 0.1)
|
|
68
77
|
signet (>= 0.16, < 2.a)
|
|
69
|
-
|
|
70
|
-
i18n (1.14.6)
|
|
78
|
+
i18n (1.15.2)
|
|
71
79
|
concurrent-ruby (~> 1.0)
|
|
72
80
|
io-console (0.7.2)
|
|
73
81
|
irb (1.14.1)
|
|
74
82
|
rdoc (>= 4.0.0)
|
|
75
83
|
reline (>= 0.4.2)
|
|
76
84
|
json (2.7.5)
|
|
77
|
-
jwt (
|
|
85
|
+
jwt (3.3.0)
|
|
78
86
|
base64
|
|
79
87
|
language_server-protocol (3.17.0.3)
|
|
80
|
-
logger (1.
|
|
88
|
+
logger (1.7.0)
|
|
81
89
|
mini_mime (1.1.5)
|
|
90
|
+
mini_portile2 (2.8.9)
|
|
82
91
|
minitest (5.25.1)
|
|
83
92
|
minitest-reporters (1.7.1)
|
|
84
93
|
ansi
|
|
85
94
|
builder
|
|
86
95
|
minitest (>= 5.0)
|
|
87
96
|
ruby-progressbar
|
|
88
|
-
multi_json (1.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
multi_json (1.21.2)
|
|
98
|
+
net-http (0.9.1)
|
|
99
|
+
uri (>= 0.11.1)
|
|
100
|
+
nokogiri (1.19.4)
|
|
101
|
+
mini_portile2 (~> 2.8.2)
|
|
102
|
+
racc (~> 1.4)
|
|
92
103
|
os (1.1.4)
|
|
93
104
|
parallel (1.26.3)
|
|
94
105
|
parser (3.3.5.1)
|
|
95
106
|
ast (~> 2.4.1)
|
|
96
107
|
racc
|
|
108
|
+
pstore (0.2.1)
|
|
97
109
|
psych (5.1.2)
|
|
98
110
|
stringio
|
|
99
|
-
public_suffix (
|
|
111
|
+
public_suffix (7.0.5)
|
|
100
112
|
racc (1.8.1)
|
|
101
113
|
rainbow (3.1.1)
|
|
102
114
|
rake (13.2.1)
|
|
@@ -105,11 +117,11 @@ GEM
|
|
|
105
117
|
regexp_parser (2.9.2)
|
|
106
118
|
reline (0.5.10)
|
|
107
119
|
io-console (~> 0.5)
|
|
108
|
-
representable (3.
|
|
120
|
+
representable (3.3.1)
|
|
109
121
|
declarative (< 0.1.0)
|
|
110
122
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
|
111
123
|
uber (< 0.2.0)
|
|
112
|
-
retriable (
|
|
124
|
+
retriable (4.2.0)
|
|
113
125
|
rubocop (1.68.0)
|
|
114
126
|
json (~> 2.3)
|
|
115
127
|
language_server-protocol (>= 3.17.0)
|
|
@@ -123,19 +135,22 @@ GEM
|
|
|
123
135
|
rubocop-ast (1.33.1)
|
|
124
136
|
parser (>= 3.3.1.0)
|
|
125
137
|
ruby-progressbar (1.13.0)
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
rubyXL (3.4.38)
|
|
139
|
+
nokogiri (>= 1.10.8)
|
|
140
|
+
rubyzip (>= 3.2.2)
|
|
141
|
+
rubyzip (3.7.0)
|
|
142
|
+
securerandom (0.4.1)
|
|
143
|
+
signet (0.22.0)
|
|
128
144
|
addressable (~> 2.8)
|
|
129
145
|
faraday (>= 0.17.5, < 3.a)
|
|
130
|
-
jwt (>= 1.5, <
|
|
131
|
-
multi_json (~> 1.10)
|
|
146
|
+
jwt (>= 1.5, < 4.0)
|
|
132
147
|
stringio (3.1.1)
|
|
133
148
|
trailblazer-option (0.1.2)
|
|
134
149
|
tzinfo (2.0.6)
|
|
135
150
|
concurrent-ruby (~> 1.0)
|
|
136
151
|
uber (0.1.0)
|
|
137
152
|
unicode-display_width (2.6.0)
|
|
138
|
-
uri (
|
|
153
|
+
uri (1.1.1)
|
|
139
154
|
|
|
140
155
|
PLATFORMS
|
|
141
156
|
ruby
|
|
@@ -150,5 +165,8 @@ DEPENDENCIES
|
|
|
150
165
|
rake (>= 12.3.0, < 14.0.0)
|
|
151
166
|
rubocop (~> 1.0)
|
|
152
167
|
|
|
168
|
+
RUBY VERSION
|
|
169
|
+
ruby 3.2.2p53
|
|
170
|
+
|
|
153
171
|
BUNDLED WITH
|
|
154
172
|
2.3.10
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AdLocalize
|
|
2
2
|
|
|
3
|
-
The purpose of this gem is to automatically generate wording files from a CSV input (CSV file or Google Spreadsheet).
|
|
3
|
+
The purpose of this gem is to automatically generate wording files from a CSV or Excel input (CSV file, `.xlsx` file or Google Spreadsheet).
|
|
4
4
|
It supports iOS, Android, JSON, YAML and Java Properties.
|
|
5
5
|
It is a useful tool when working on a mobile application or a SPA.
|
|
6
6
|
|
|
@@ -46,6 +46,23 @@ $ ad_localize -k <your-spreadsheet-drive-key>
|
|
|
46
46
|
$ ad_localize -k <your-spreadsheet-drive-key> -s <comma-separated-sheet-id-list>
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Google is the default download source. Use `--excel-file` to convert Microsoft Excel `.xlsx` sources before export.
|
|
50
|
+
|
|
51
|
+
* Export wording from a local Excel file. Only `.xlsx` files are supported. This can be used as an offline fallback.
|
|
52
|
+
```
|
|
53
|
+
$ ad_localize --excel-file wordings.xlsx
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
* Export wording from an Excel sharing URL. Remote Excel files are downloaded through Microsoft Graph API and require a `MICROSOFT_GRAPH_ACCESS_TOKEN` environment variable.
|
|
57
|
+
```
|
|
58
|
+
$ MICROSOFT_GRAPH_ACCESS_TOKEN=<token> ad_localize --excel-file https://example.sharepoint.com/.../wordings.xlsx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
* Export wording from specific Excel sheets.
|
|
62
|
+
```
|
|
63
|
+
$ ad_localize --excel-file wordings.xlsx -s <comma-separated-sheet-name-list>
|
|
64
|
+
```
|
|
65
|
+
|
|
49
66
|
* Export wording from a private google spreadsheet. It requires a [Google Cloud Service Account](#using-a-google-cloud-service-account).
|
|
50
67
|
```
|
|
51
68
|
$ GOOGLE_APPLICATION_CREDENTIALS=<path-to-client-secret.json> ad_localize -k <your-spreadsheet-drive-key>
|
|
@@ -91,6 +108,32 @@ $ ad_localize -l fr,en
|
|
|
91
108
|
$ ad_localize --skip-value-stripping
|
|
92
109
|
```
|
|
93
110
|
|
|
111
|
+
### Download from an external source with the browser
|
|
112
|
+
|
|
113
|
+
When the wording file must first be downloaded from an external source (for example a SharePoint share link) and no API access is available, `scripts/download_with_navigator` downloads it through your browser, so that your browser session and cookies are used.
|
|
114
|
+
|
|
115
|
+
The script opens the URL in the default browser, waits for the download to complete in `~/Downloads`, moves the file to a temporary directory (or to the `-o` path) and prints its path on stdout.
|
|
116
|
+
|
|
117
|
+
If you download the script on its own (outside of this repository), make it executable with `chmod +x download_with_navigator`, or run it with `python3 download_with_navigator ...`.
|
|
118
|
+
|
|
119
|
+
* Download a file from its raw URL
|
|
120
|
+
```
|
|
121
|
+
$ scripts/download_with_navigator <url>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
* Download a file from a SharePoint share link
|
|
125
|
+
```
|
|
126
|
+
$ scripts/download_with_navigator <sharepoint share url> --source sharepoint
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
* Download a file then export its wording with ad_localize
|
|
130
|
+
```
|
|
131
|
+
$ XLSX_FILE=$(scripts/download_with_navigator <sharepoint share url> --source sharepoint)
|
|
132
|
+
$ ad_localize --excel-file "$XLSX_FILE"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Run `scripts/download_with_navigator -h` for all options, and `scripts/download_with_navigator --version` to check the version of a downloaded copy. The `DOWNLOADS_DIR` (default `~/Downloads`) and `DOWNLOAD_TIMEOUT` (default `180` seconds) environment variables can be used to change the watched directory and the wait duration.
|
|
136
|
+
|
|
94
137
|
### In a Ruby program
|
|
95
138
|
There are many possibilities when using AdLocalize in a ruby program. You can add support to your own wording format, support other platforms, select which locales you want to export, generate wording file content without writing on the disk and many more.
|
|
96
139
|
|
|
@@ -105,7 +148,8 @@ If you want more examples, please open a documentation issue.
|
|
|
105
148
|
export_request.sheet_ids = %w[first second]
|
|
106
149
|
export_request.verbose = true
|
|
107
150
|
begin
|
|
108
|
-
# download
|
|
151
|
+
# download Google spreadsheets and convert Excel files to CSV tempfiles
|
|
152
|
+
# be sure that GOOGLE_APPLICATION_CREDENTIALS is set if you use service account
|
|
109
153
|
export_request.downloaded_csvs = AdLocalize::Interactors::DownloadSpreadsheets.new.call(export_request: export_request)
|
|
110
154
|
# execute request
|
|
111
155
|
AdLocalize::Interactors::ProcessExportRequest.new.call(export_request: export_request)
|
data/ad_localize.gemspec
CHANGED
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
|
|
28
28
|
spec.summary = %q(AdLocalize helps with mobile and web applications wording)
|
|
29
29
|
spec.description = %q(AdLocalize produces localization files from platform agnostic wording.
|
|
30
|
-
Supported wording format : CSV. Supported export format: iOS, Android, JSON and YAML)
|
|
30
|
+
Supported wording format : CSV, XLSX. Supported export format: iOS, Android, JSON and YAML)
|
|
31
31
|
spec.homepage = 'https://github.com/applidium/ad_localize'
|
|
32
32
|
|
|
33
33
|
# Specify which files should be added to the gem when it is released.
|
|
@@ -41,9 +41,11 @@ Gem::Specification.new do |spec|
|
|
|
41
41
|
|
|
42
42
|
spec.add_dependency 'activesupport', '>= 6.1.3.2', '< 8.0'
|
|
43
43
|
spec.add_dependency 'colorize', '~> 1.0'
|
|
44
|
+
spec.add_dependency 'csv', '~> 3.2', '>= 3.2.7'
|
|
44
45
|
spec.add_dependency 'google-apis-sheets_v4', '~> 0.9'
|
|
45
46
|
spec.add_dependency 'google-apis-drive_v3', '~> 0.33.0'
|
|
47
|
+
spec.add_dependency 'rubyXL', '~> 3.4'
|
|
46
48
|
|
|
47
|
-
spec.required_ruby_version = '>= 3.
|
|
49
|
+
spec.required_ruby_version = '>= 3.2', '< 4.0'
|
|
48
50
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
49
51
|
end
|
data/lib/ad_localize/cli.rb
CHANGED
|
@@ -5,7 +5,7 @@ module AdLocalize
|
|
|
5
5
|
options = OptionHandler.parse!(args)
|
|
6
6
|
export_request = Mappers::OptionsToExportRequest.new.map(options: options)
|
|
7
7
|
LOGGER.debug("Export request options : #{export_request}")
|
|
8
|
-
export_request.downloaded_csvs = Interactors::DownloadSpreadsheets.new.call(export_request: export_request)
|
|
8
|
+
export_request.downloaded_csvs = Interactors::DownloadSpreadsheets.new.call(export_request: export_request)
|
|
9
9
|
Interactors::ProcessExportRequest.new.call(export_request: export_request)
|
|
10
10
|
ensure
|
|
11
11
|
export_request&.downloaded_csvs&.each do |file|
|
|
@@ -4,9 +4,19 @@ module AdLocalize
|
|
|
4
4
|
class DownloadSpreadsheets
|
|
5
5
|
def initialize
|
|
6
6
|
@drive_repository = Repositories::DriveRepository.new
|
|
7
|
+
@office_repository = Repositories::OfficeRepository.new
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def call(export_request:)
|
|
11
|
+
return download_google_spreadsheets(export_request: export_request) if export_request.has_sheets?
|
|
12
|
+
return download_office_spreadsheets(export_request: export_request) if export_request.has_excel_file?
|
|
13
|
+
|
|
14
|
+
[]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def download_google_spreadsheets(export_request:)
|
|
10
20
|
LOGGER.debug("Downloading spreadsheets")
|
|
11
21
|
if export_request.export_all
|
|
12
22
|
@drive_repository.download_all_sheets(spreadsheet_id: export_request.spreadsheet_id)
|
|
@@ -15,6 +25,12 @@ module AdLocalize
|
|
|
15
25
|
sheet_ids: export_request.sheet_ids)
|
|
16
26
|
end
|
|
17
27
|
end
|
|
28
|
+
|
|
29
|
+
def download_office_spreadsheets(export_request:)
|
|
30
|
+
@office_repository.convert_to_csvs(paths: [export_request.excel_file],
|
|
31
|
+
sheet_ids: export_request.sheet_ids,
|
|
32
|
+
export_all: export_request.export_all)
|
|
33
|
+
end
|
|
18
34
|
end
|
|
19
35
|
end
|
|
20
36
|
end
|
|
@@ -32,7 +32,8 @@ module AdLocalize
|
|
|
32
32
|
|
|
33
33
|
def merge_compound_wordings(reference_hash:, new_hash:, merge_policy:)
|
|
34
34
|
new_hash.each do |new_label, new_list|
|
|
35
|
-
|
|
35
|
+
# key? instead of [] : plurals and adaptives hashes create an empty entry on read
|
|
36
|
+
if !reference_hash.key?(new_label)
|
|
36
37
|
reference_hash[new_label] = new_list
|
|
37
38
|
elsif merge_policy == REPLACE_MERGE_POLICY
|
|
38
39
|
merge_simple_wordings(reference_list: reference_hash[new_label], new_list: new_list, merge_policy: merge_policy)
|
|
@@ -9,6 +9,7 @@ module AdLocalize
|
|
|
9
9
|
request.auto_escape_percent = options[:'auto-escape-percent']
|
|
10
10
|
request.skip_value_stripping = options[:'skip-value-stripping']
|
|
11
11
|
request.csv_paths = options[:csv_paths]
|
|
12
|
+
request.excel_file = options[:'excel-file']
|
|
12
13
|
request.merge_policy = options[:'merge-policy']
|
|
13
14
|
request.output_path = options[:'target-dir']
|
|
14
15
|
request.platforms = options[:only]
|
|
@@ -7,6 +7,7 @@ module AdLocalize
|
|
|
7
7
|
:'auto-escape-percent' => Requests::ExportRequest::DEFAULTS[:auto_escape_percent],
|
|
8
8
|
:'skip-value-stripping' => Requests::ExportRequest::DEFAULTS[:skip_value_stripping],
|
|
9
9
|
csv_paths: Requests::ExportRequest::DEFAULTS[:csv_paths],
|
|
10
|
+
:'excel-file' => Requests::ExportRequest::DEFAULTS[:excel_file],
|
|
10
11
|
:'merge-policy' => Requests::ExportRequest::DEFAULTS[:merge_policy],
|
|
11
12
|
:'target-dir' => Requests::ExportRequest::DEFAULTS[:output_path],
|
|
12
13
|
:'drive-key' => Requests::ExportRequest::DEFAULTS[:spreadsheet_id],
|
|
@@ -17,7 +18,7 @@ module AdLocalize
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
def self.parse!(options)
|
|
20
|
-
args = DEFAULT_OPTIONS
|
|
21
|
+
args = DEFAULT_OPTIONS.deep_dup
|
|
21
22
|
OptionParser.new do |parser|
|
|
22
23
|
parser.banner = 'Usage: exe/ad_localize [options] file(s)'
|
|
23
24
|
parser.on("-d", "--debug", TrueClass, 'Run in debug mode')
|
|
@@ -31,8 +32,7 @@ module AdLocalize
|
|
|
31
32
|
'LOCALES is a comma separated list. Only generate localisation files for the specified locales')
|
|
32
33
|
merge_policy_option(parser)
|
|
33
34
|
platforms_option(parser)
|
|
34
|
-
parser
|
|
35
|
-
'SHEET_IDS is a comma separated list. Use a specific sheet id for Google Drive spreadsheets with several sheets')
|
|
35
|
+
spreadsheet_options(parser)
|
|
36
36
|
parser.on("-t", "--target-dir PATH", String, 'Path to the target directory')
|
|
37
37
|
parser.on("-v", "--version", 'Prints current version') do
|
|
38
38
|
puts AdLocalize::VERSION
|
|
@@ -43,15 +43,16 @@ module AdLocalize
|
|
|
43
43
|
parser.on("--skip-value-stripping", TrueClass, 'Disable the removal of leading and trailing whitespaces on wording values')
|
|
44
44
|
end.parse!(options, into: args)
|
|
45
45
|
|
|
46
|
+
validate_download_source!(args)
|
|
46
47
|
args[:csv_paths] = options
|
|
47
48
|
return args
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
def self.export_all_option(parser)
|
|
51
52
|
export_all_option = <<~DOC
|
|
52
|
-
Export all sheets from spreadsheet specified by --drive-key option.
|
|
53
|
-
\
|
|
54
|
-
\
|
|
53
|
+
Export all sheets from spreadsheet specified by --drive-key or --excel-file option.
|
|
54
|
+
\tSheets are merged into a single export (see -m|--merge-policy option to handle duplicated keys).
|
|
55
|
+
\tWith --drive-key, an GCLOUD_CLIENT_SECRET environment variable containing the client_secret.json content is needed.
|
|
55
56
|
DOC
|
|
56
57
|
parser.on("-e", "--export-all-sheets", TrueClass, export_all_option)
|
|
57
58
|
end
|
|
@@ -65,6 +66,18 @@ module AdLocalize
|
|
|
65
66
|
parser.on("-m", "--merge-policy POLICY", Interactors::MergeWordings::MERGE_POLICIES, merge_policy_option)
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
def self.spreadsheet_options(parser)
|
|
70
|
+
parser.on("-f", "--excel-file XLSX_PATH_OR_URL", String, 'Use a Microsoft Excel .xlsx file path or sharing URL')
|
|
71
|
+
parser.on("-s", "--sheets SHEET_IDS", Array,
|
|
72
|
+
'SHEET_IDS is a comma separated list. Use specific sheet ids for Google Drive spreadsheets or sheet names for Excel files')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.validate_download_source!(args)
|
|
76
|
+
return unless args[:'drive-key'].present? && args[:'excel-file'].present?
|
|
77
|
+
|
|
78
|
+
raise OptionParser::InvalidOption.new('Use either --drive-key or --excel-file, not both')
|
|
79
|
+
end
|
|
80
|
+
|
|
68
81
|
def self.platforms_option(parser)
|
|
69
82
|
platforms_option = <<~DOC
|
|
70
83
|
PLATFORMS is a comma separated list.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Repositories
|
|
4
|
+
class OfficeRepository
|
|
5
|
+
EXCEL_EXTENSION = '.xlsx'
|
|
6
|
+
GRAPH_ACCESS_TOKEN_ENV = 'MICROSOFT_GRAPH_ACCESS_TOKEN'
|
|
7
|
+
GRAPH_URL = 'https://graph.microsoft.com/v1.0'
|
|
8
|
+
|
|
9
|
+
def office_source?(path:)
|
|
10
|
+
File.extname(URI(path.to_s).path) == EXCEL_EXTENSION
|
|
11
|
+
rescue URI::InvalidURIError
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def convert_to_csvs(paths:, sheet_ids:, export_all: false)
|
|
16
|
+
paths.flat_map do |path|
|
|
17
|
+
convert_to_csv(path: path, sheet_ids: sheet_ids, export_all: export_all)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def convert_to_csv(path:, sheet_ids:, export_all:)
|
|
24
|
+
source_file = download(path: path) if remote_source?(path)
|
|
25
|
+
workbook = RubyXL::Parser.parse(source_file&.path || path.to_s)
|
|
26
|
+
worksheets = worksheets_for(workbook: workbook, sheet_ids: sheet_ids, path: path, export_all: export_all)
|
|
27
|
+
worksheets.map { |worksheet| write_csv(worksheet: worksheet) }
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
LOGGER.error("Cannot convert Excel file #{path}. Error: #{e.message}")
|
|
30
|
+
[]
|
|
31
|
+
ensure
|
|
32
|
+
source_file&.close
|
|
33
|
+
source_file&.unlink
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def download(path:)
|
|
37
|
+
file = Tempfile.new(['ad_localize', EXCEL_EXTENSION])
|
|
38
|
+
file.binmode
|
|
39
|
+
file.write(download_body(uri: graph_content_uri(path: path)))
|
|
40
|
+
file.rewind
|
|
41
|
+
file
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def download_body(uri:, limit: 5)
|
|
45
|
+
raise 'Too many redirects' if limit.zero?
|
|
46
|
+
|
|
47
|
+
response = get(uri: uri)
|
|
48
|
+
case response.code.to_i
|
|
49
|
+
when 200..299
|
|
50
|
+
response.body
|
|
51
|
+
when 300..399
|
|
52
|
+
redirect_uri = URI.join(uri, response['location'])
|
|
53
|
+
raise "Refusing to follow non-https redirect to #{redirect_uri.hostname}" unless redirect_uri.scheme == 'https'
|
|
54
|
+
|
|
55
|
+
download_body(uri: redirect_uri, limit: limit - 1)
|
|
56
|
+
else
|
|
57
|
+
raise "HTTP #{response.code}: #{response.body}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get(uri:)
|
|
62
|
+
request = Net::HTTP::Get.new(uri)
|
|
63
|
+
# Graph redirects to a pre-authenticated download URL: never leak the token to another host
|
|
64
|
+
request['Authorization'] = "Bearer #{graph_access_token}" if uri.hostname == URI(GRAPH_URL).hostname
|
|
65
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
66
|
+
http.request(request)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def graph_content_uri(path:)
|
|
71
|
+
URI("#{GRAPH_URL}/shares/#{share_id(url: path.to_s)}/driveItem/content")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def share_id(url:)
|
|
75
|
+
encoded_url = Base64.urlsafe_encode64(url, padding: false)
|
|
76
|
+
"u!#{encoded_url}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def graph_access_token
|
|
80
|
+
ENV.fetch(GRAPH_ACCESS_TOKEN_ENV) do
|
|
81
|
+
raise "Missing #{GRAPH_ACCESS_TOKEN_ENV} environment variable"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def worksheets_for(workbook:, sheet_ids:, path:, export_all:)
|
|
86
|
+
return workbook.worksheets if export_all
|
|
87
|
+
return [workbook.worksheets.first] if sheet_ids == Requests::ExportRequest::DEFAULTS[:sheet_ids]
|
|
88
|
+
|
|
89
|
+
sheet_ids.filter_map do |sheet_id|
|
|
90
|
+
worksheet = workbook.worksheets.find { |sheet| sheet.sheet_name == sheet_id }
|
|
91
|
+
LOGGER.error("Cannot find Excel sheet named #{sheet_id} in #{path}") unless worksheet
|
|
92
|
+
worksheet
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def write_csv(worksheet:)
|
|
97
|
+
file = Tempfile.new(['ad_localize', '.csv'])
|
|
98
|
+
CSV.open(file.path, 'w') do |csv|
|
|
99
|
+
worksheet.each do |row|
|
|
100
|
+
csv << (row&.cells || []).map { |cell| cell&.value }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
file.rewind
|
|
104
|
+
file
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def remote_source?(path)
|
|
108
|
+
raise "Only https URLs are supported for Excel files: #{path}" if path.to_s.start_with?('http://')
|
|
109
|
+
|
|
110
|
+
path.to_s.start_with?('https://')
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -8,6 +8,7 @@ module AdLocalize
|
|
|
8
8
|
auto_escape_percent: false,
|
|
9
9
|
skip_value_stripping: false,
|
|
10
10
|
csv_paths: [],
|
|
11
|
+
excel_file: nil,
|
|
11
12
|
merge_policy: Interactors::MergeWordings::DEFAULT_POLICY,
|
|
12
13
|
output_path: Pathname.new('exports'),
|
|
13
14
|
spreadsheet_id: nil,
|
|
@@ -26,6 +27,7 @@ module AdLocalize
|
|
|
26
27
|
:auto_escape_percent,
|
|
27
28
|
:skip_value_stripping,
|
|
28
29
|
:csv_paths,
|
|
30
|
+
:excel_file,
|
|
29
31
|
:merge_policy,
|
|
30
32
|
:output_path,
|
|
31
33
|
:platforms,
|
|
@@ -42,6 +44,7 @@ module AdLocalize
|
|
|
42
44
|
@auto_escape_percent = DEFAULTS[:auto_escape_percent]
|
|
43
45
|
@skip_value_stripping = DEFAULTS[:skip_value_stripping]
|
|
44
46
|
@csv_paths = DEFAULTS[:csv_paths]
|
|
47
|
+
@excel_file = DEFAULTS[:excel_file]
|
|
45
48
|
@merge_policy = DEFAULTS[:merge_policy]
|
|
46
49
|
@output_path = DEFAULTS[:output_path]
|
|
47
50
|
@platforms = DEFAULTS[:platforms]
|
|
@@ -76,6 +79,10 @@ module AdLocalize
|
|
|
76
79
|
@csv_paths = value.compact.map { |path| Pathname.new(path) }
|
|
77
80
|
end
|
|
78
81
|
|
|
82
|
+
def excel_file=(value)
|
|
83
|
+
@excel_file = Pathname.new(value) unless value.blank?
|
|
84
|
+
end
|
|
85
|
+
|
|
79
86
|
def merge_policy=(value)
|
|
80
87
|
@merge_policy = value unless value.blank?
|
|
81
88
|
end
|
|
@@ -118,6 +125,10 @@ module AdLocalize
|
|
|
118
125
|
spreadsheet_id.present?
|
|
119
126
|
end
|
|
120
127
|
|
|
128
|
+
def has_excel_file?
|
|
129
|
+
excel_file.present?
|
|
130
|
+
end
|
|
131
|
+
|
|
121
132
|
def has_csv_paths?
|
|
122
133
|
all_csv_paths.present?
|
|
123
134
|
end
|
|
@@ -136,6 +147,7 @@ module AdLocalize
|
|
|
136
147
|
"auto_escape_percent: #{auto_escape_percent}, " \
|
|
137
148
|
"skip_value_stripping: #{skip_value_stripping}, " \
|
|
138
149
|
"csv_paths: #{csv_paths}, " \
|
|
150
|
+
"excel_file: #{excel_file}, " \
|
|
139
151
|
"merge_policy: #{merge_policy}, " \
|
|
140
152
|
"output_path: #{output_path}, " \
|
|
141
153
|
"spreadsheet_id: #{spreadsheet_id}, " \
|
data/lib/ad_localize/version.rb
CHANGED
data/lib/ad_localize.rb
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'active_support'
|
|
3
3
|
require 'active_support/core_ext'
|
|
4
|
+
require 'base64'
|
|
4
5
|
require 'fileutils'
|
|
5
6
|
require 'pathname'
|
|
6
7
|
require 'yaml'
|
|
7
8
|
require 'json'
|
|
8
9
|
require 'csv'
|
|
9
10
|
require 'logger'
|
|
11
|
+
require 'net/http'
|
|
12
|
+
require 'uri'
|
|
10
13
|
require 'colorize'
|
|
11
14
|
require 'optparse'
|
|
12
15
|
require 'erb'
|
|
16
|
+
require 'tempfile'
|
|
13
17
|
require 'googleauth'
|
|
14
18
|
require 'google/apis/sheets_v4'
|
|
15
19
|
require 'google/apis/drive_v3'
|
|
20
|
+
require 'rubyXL'
|
|
16
21
|
|
|
17
22
|
require 'ad_localize/repositories/drive_repository'
|
|
18
23
|
require 'ad_localize/repositories/file_system_repository'
|
|
24
|
+
require 'ad_localize/repositories/office_repository'
|
|
19
25
|
|
|
20
26
|
require 'ad_localize/view_models/simple_wording_view_model'
|
|
21
27
|
require 'ad_localize/view_models/compound_wording_view_model'
|
data/mise.toml
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Download a file through the user browser, so that the download uses the browser session and cookies.
|
|
4
|
+
# The URL is opened as is, unless a --source option tells how to turn it into a download URL :
|
|
5
|
+
#
|
|
6
|
+
# --source sharepoint : the URL is a SharePoint share link, its query is replaced with download=1
|
|
7
|
+
#
|
|
8
|
+
# This script can be used with ad_localize like this :
|
|
9
|
+
#
|
|
10
|
+
# XLSX_FILE=$(./download_with_navigator <sharepoint share url> --source sharepoint)
|
|
11
|
+
# ad_localize --excel-file "$XLSX_FILE"
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
import time
|
|
20
|
+
import webbrowser
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from urllib.parse import urlencode, urlparse, urlunparse
|
|
23
|
+
|
|
24
|
+
VERSION = "1.0.0"
|
|
25
|
+
|
|
26
|
+
# Browsers write to a temporary name and rename it when the download is complete
|
|
27
|
+
PARTIAL_SUFFIXES = (".crdownload", ".part", ".download", ".tmp")
|
|
28
|
+
STABLE_CHECKS = 3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def sharepoint_url(url):
|
|
32
|
+
parsed = urlparse(url)
|
|
33
|
+
|
|
34
|
+
return urlunparse(parsed._replace(query=urlencode({"download": "1"}), fragment=""))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
SOURCES = {
|
|
38
|
+
"sharepoint": sharepoint_url,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def download_url(url, source):
|
|
43
|
+
parsed = urlparse(url)
|
|
44
|
+
|
|
45
|
+
if parsed.scheme not in ("http", "https") or not parsed.hostname:
|
|
46
|
+
raise ValueError(f"http(s) URL expected: {url}")
|
|
47
|
+
|
|
48
|
+
return SOURCES[source](url) if source else url
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def completed_files(downloads_dir):
|
|
52
|
+
return [
|
|
53
|
+
p for p in Path(downloads_dir).iterdir()
|
|
54
|
+
if p.is_file()
|
|
55
|
+
and not p.name.startswith(".")
|
|
56
|
+
and p.suffix.lower() not in PARTIAL_SUFFIXES
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def snapshot(downloads_dir):
|
|
61
|
+
return {p.name: p.stat().st_mtime_ns for p in completed_files(downloads_dir)}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def wait_for_download(downloads_dir, baseline, timeout):
|
|
65
|
+
deadline = time.monotonic() + timeout
|
|
66
|
+
last_seen = None
|
|
67
|
+
stable_count = 0
|
|
68
|
+
|
|
69
|
+
while time.monotonic() < deadline:
|
|
70
|
+
candidates = [
|
|
71
|
+
p for p in completed_files(downloads_dir)
|
|
72
|
+
if baseline.get(p.name) != p.stat().st_mtime_ns
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
if candidates:
|
|
76
|
+
newest = max(candidates, key=lambda p: p.stat().st_mtime_ns)
|
|
77
|
+
current = (newest, newest.stat().st_size)
|
|
78
|
+
|
|
79
|
+
stable_count = stable_count + 1 if current == last_seen else 1
|
|
80
|
+
last_seen = current
|
|
81
|
+
|
|
82
|
+
if current[1] > 0 and stable_count >= STABLE_CHECKS:
|
|
83
|
+
return newest
|
|
84
|
+
|
|
85
|
+
time.sleep(1)
|
|
86
|
+
|
|
87
|
+
raise TimeoutError(f"No file downloaded in {downloads_dir}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
parser = argparse.ArgumentParser(
|
|
92
|
+
description="Download a file through the browser, using the user session"
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument("url", help="URL of the file to download")
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
"--source",
|
|
97
|
+
choices=sorted(SOURCES),
|
|
98
|
+
help="Origin of the URL, used to turn it into a download URL. If missing, the URL is opened as is.",
|
|
99
|
+
)
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"-o",
|
|
102
|
+
"--output",
|
|
103
|
+
help="Output file. If missing, a temporary file is used.",
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {VERSION}")
|
|
106
|
+
args = parser.parse_args()
|
|
107
|
+
|
|
108
|
+
downloads_dir = os.environ.get("DOWNLOADS_DIR", str(Path.home() / "Downloads"))
|
|
109
|
+
timeout = int(os.environ.get("DOWNLOAD_TIMEOUT", "180"))
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
url = download_url(args.url, args.source)
|
|
113
|
+
baseline = snapshot(downloads_dir)
|
|
114
|
+
|
|
115
|
+
print("Opening browser...", file=sys.stderr)
|
|
116
|
+
webbrowser.open(url)
|
|
117
|
+
|
|
118
|
+
downloaded_file = wait_for_download(downloads_dir, baseline, timeout)
|
|
119
|
+
except (ValueError, TimeoutError, OSError) as e:
|
|
120
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
|
|
123
|
+
if args.output:
|
|
124
|
+
final_file = Path(args.output).resolve()
|
|
125
|
+
final_file.parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
else:
|
|
127
|
+
final_file = Path(tempfile.mkdtemp(prefix="navigator-download-")) / downloaded_file.name
|
|
128
|
+
|
|
129
|
+
shutil.move(downloaded_file, final_file)
|
|
130
|
+
|
|
131
|
+
print("Download succeeded:", final_file, file=sys.stderr)
|
|
132
|
+
print(final_file)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
main()
|
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: 6.
|
|
4
|
+
version: 6.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Edouard Siegel
|
|
@@ -21,7 +21,7 @@ authors:
|
|
|
21
21
|
autorequire:
|
|
22
22
|
bindir: exe
|
|
23
23
|
cert_chain: []
|
|
24
|
-
date:
|
|
24
|
+
date: 2026-10-02 00:00:00.000000000 Z
|
|
25
25
|
dependencies:
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activesupport
|
|
@@ -57,6 +57,26 @@ dependencies:
|
|
|
57
57
|
- - "~>"
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '1.0'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: csv
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.2'
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 3.2.7
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '3.2'
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 3.2.7
|
|
60
80
|
- !ruby/object:Gem::Dependency
|
|
61
81
|
name: google-apis-sheets_v4
|
|
62
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -85,9 +105,23 @@ dependencies:
|
|
|
85
105
|
- - "~>"
|
|
86
106
|
- !ruby/object:Gem::Version
|
|
87
107
|
version: 0.33.0
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: rubyXL
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - "~>"
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '3.4'
|
|
115
|
+
type: :runtime
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - "~>"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '3.4'
|
|
88
122
|
description: |-
|
|
89
123
|
AdLocalize produces localization files from platform agnostic wording.
|
|
90
|
-
Supported wording format : CSV. Supported export format: iOS, Android, JSON and YAML
|
|
124
|
+
Supported wording format : CSV, XLSX. Supported export format: iOS, Android, JSON and YAML
|
|
91
125
|
email:
|
|
92
126
|
- joanna.vigne@fabernovel.com
|
|
93
127
|
- pierre.felgines@fabernovel.com
|
|
@@ -144,6 +178,7 @@ files:
|
|
|
144
178
|
- lib/ad_localize/parsers/key_parser.rb
|
|
145
179
|
- lib/ad_localize/repositories/drive_repository.rb
|
|
146
180
|
- lib/ad_localize/repositories/file_system_repository.rb
|
|
181
|
+
- lib/ad_localize/repositories/office_repository.rb
|
|
147
182
|
- lib/ad_localize/requests/export_request.rb
|
|
148
183
|
- lib/ad_localize/sanitizers/ios_sanitizer.rb
|
|
149
184
|
- lib/ad_localize/sanitizers/ios_to_android_sanitizer.rb
|
|
@@ -164,6 +199,8 @@ files:
|
|
|
164
199
|
- lib/ad_localize/version.rb
|
|
165
200
|
- lib/ad_localize/view_models/compound_wording_view_model.rb
|
|
166
201
|
- lib/ad_localize/view_models/simple_wording_view_model.rb
|
|
202
|
+
- mise.toml
|
|
203
|
+
- scripts/download_with_navigator
|
|
167
204
|
homepage: https://github.com/applidium/ad_localize
|
|
168
205
|
licenses:
|
|
169
206
|
- MIT
|
|
@@ -177,7 +214,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
177
214
|
requirements:
|
|
178
215
|
- - ">="
|
|
179
216
|
- !ruby/object:Gem::Version
|
|
180
|
-
version: '3.
|
|
217
|
+
version: '3.2'
|
|
181
218
|
- - "<"
|
|
182
219
|
- !ruby/object:Gem::Version
|
|
183
220
|
version: '4.0'
|
|
@@ -187,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
187
224
|
- !ruby/object:Gem::Version
|
|
188
225
|
version: '0'
|
|
189
226
|
requirements: []
|
|
190
|
-
rubygems_version: 3.
|
|
227
|
+
rubygems_version: 3.4.10
|
|
191
228
|
signing_key:
|
|
192
229
|
specification_version: 4
|
|
193
230
|
summary: AdLocalize helps with mobile and web applications wording
|