trip_advisor 0.0.15
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/.document +5 -0
- data/.gitignore +52 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +118 -0
- data/LICENSE.txt +20 -0
- data/README.md +23 -0
- data/Rakefile +7 -0
- data/lib/trip_advisor/api.rb +45 -0
- data/lib/trip_advisor/build.rb +221 -0
- data/lib/trip_advisor/rake_tasks.rb +182 -0
- data/lib/trip_advisor/resource_manager.rb +101 -0
- data/lib/trip_advisor/strings_file.rb +112 -0
- data/lib/trip_advisor/translation.rb +63 -0
- data/lib/trip_advisor/translation_tool.rb +110 -0
- data/lib/trip_advisor/version.rb +3 -0
- data/lib/trip_advisor.rb +5 -0
- data/run_tests.sh +6 -0
- data/spec/api_spec.rb +34 -0
- data/spec/builder_spec.rb +262 -0
- data/spec/fixtures/AndroidStrings.txt +21 -0
- data/spec/fixtures/Strings.txt +47 -0
- data/spec/fixtures/project.git/.gitignore +1 -0
- data/spec/fixtures/project.git/BUILD.json +7 -0
- data/spec/fixtures/project.git/Code/Project.h +0 -0
- data/spec/fixtures/project.git/Code/Project.m +0 -0
- data/spec/fixtures/project.git/Gemfile +3 -0
- data/spec/fixtures/project.git/Gemfile.lock +38 -0
- data/spec/fixtures/project.git/HEAD +1 -0
- data/spec/fixtures/project.git/LICENSE +1 -0
- data/spec/fixtures/project.git/Podfile +6 -0
- data/spec/fixtures/project.git/Podfile.lock +14 -0
- data/spec/fixtures/project.git/Project.podspec +14 -0
- data/spec/fixtures/project.git/TestProject/Images.xcassets/AppIcon.appiconset/Contents.json +23 -0
- data/spec/fixtures/project.git/TestProject/Images.xcassets/LaunchImage.launchimage/Contents.json +23 -0
- data/spec/fixtures/project.git/TestProject/TATPAppDelegate.h +15 -0
- data/spec/fixtures/project.git/TestProject/TATPAppDelegate.m +49 -0
- data/spec/fixtures/project.git/TestProject/TestProject-Info.plist +38 -0
- data/spec/fixtures/project.git/TestProject/TestProject-Prefix.pch +16 -0
- data/spec/fixtures/project.git/TestProject/en.lproj/InfoPlist.strings +1 -0
- data/spec/fixtures/project.git/TestProject/main.m +18 -0
- data/spec/fixtures/project.git/TestProject.xcodeproj/project.pbxproj +1020 -0
- data/spec/fixtures/project.git/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/fixtures/project.git/TestProject.xcodeproj/project.xcworkspace/xcuserdata/blake.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/spec/fixtures/project.git/TestProject.xcodeproj/xcuserdata/blake.xcuserdatad/xcschemes/TestProject.xcscheme +96 -0
- data/spec/fixtures/project.git/TestProject.xcodeproj/xcuserdata/blake.xcuserdatad/xcschemes/xcschememanagement.plist +27 -0
- data/spec/fixtures/project.git/TestProject.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/project.git/TestProjectTests/TestProjectTests-Info.plist +22 -0
- data/spec/fixtures/project.git/TestProjectTests/TestProjectTests.m +34 -0
- data/spec/fixtures/project.git/TestProjectTests/en.lproj/InfoPlist.strings +1 -0
- data/spec/fixtures/project.git/VERSION +1 -0
- data/spec/fixtures/project.git/config +7 -0
- data/spec/fixtures/project.git/description +1 -0
- data/spec/fixtures/project.git/index +0 -0
- data/spec/fixtures/project.git/info/exclude +6 -0
- data/spec/fixtures/project.git/logs/HEAD +1 -0
- data/spec/fixtures/project.git/logs/refs/heads/master +1 -0
- data/spec/fixtures/project.git/objects/00/a6b3f80ebbe4019f03461416061e8805e6eab1 +0 -0
- data/spec/fixtures/project.git/objects/03/c303d5552f29d366d11a13448c3c18e94c3bfa +0 -0
- data/spec/fixtures/project.git/objects/03/ff7c3a95a196ac8ad1d53db2bd4fb4c80507c1 +0 -0
- data/spec/fixtures/project.git/objects/0a/7123364a174ffda52d29a18cdd31fd325bbd6c +0 -0
- data/spec/fixtures/project.git/objects/0d/183cba8b0a5e477ea1d001130c222af19b8324 +0 -0
- data/spec/fixtures/project.git/objects/0e/6ba5232c3c2f8a7e435794e845b5dc9f339d8b +0 -0
- data/spec/fixtures/project.git/objects/25/5b4ee687ed1337cffb10e6d07bb89ee6e5d180 +0 -0
- data/spec/fixtures/project.git/objects/25/60a2b9ab9323ca61a0fdebd3b4af46dbe4c272 +0 -0
- data/spec/fixtures/project.git/objects/2b/f8bd93d56787a7548c7f8960a94f05c269b486 +0 -0
- data/spec/fixtures/project.git/objects/38/439105a884d2556443c7b5c645034e67fb7987 +0 -0
- data/spec/fixtures/project.git/objects/3e/efcb9dd5b38e2c1dc061052455dd97bcd51e6c +0 -0
- data/spec/fixtures/project.git/objects/53/8b3019ab68814604fbc8e017d09b690c22728c +0 -0
- data/spec/fixtures/project.git/objects/59/c178850684e21fafbebedeff15b2b522402af2 +0 -0
- data/spec/fixtures/project.git/objects/62/037867910d9aa803d7e02addc26574e270d3d8 +0 -0
- data/spec/fixtures/project.git/objects/66/05c1af131470a3b635d166f2591917b32f3b82 +0 -0
- data/spec/fixtures/project.git/objects/69/67e396037a52d8300eb9dc8a1e9f7155dd55bf +0 -0
- data/spec/fixtures/project.git/objects/6b/5ad4043aee9bc0936466760665f870d4f2f40f +0 -0
- data/spec/fixtures/project.git/objects/6c/fc746d4ecf2e3ac67bb0cf6c4013e1aff9b1f1 +0 -0
- data/spec/fixtures/project.git/objects/6f/3b0ab36651ca648408c176f81e4b2e202efa0b +0 -0
- data/spec/fixtures/project.git/objects/74/3435c9bee5c433fe166891e698fdc70d81a3de +0 -0
- data/spec/fixtures/project.git/objects/96/81bfb8ec019f74df384cfb66d68e94bd6d9005 +0 -0
- data/spec/fixtures/project.git/objects/99/fa9259e929fe3bee4791ff2a63070fc0358c0a +3 -0
- data/spec/fixtures/project.git/objects/a1/5f8980cfbf126a9a0b3ddf7fb1c3fbd6480934 +2 -0
- data/spec/fixtures/project.git/objects/a3/96706db4ec4cfca4d984d38b90dc8098bded40 +1 -0
- data/spec/fixtures/project.git/objects/b4/42134ccda2b0b1813288e7e04a401a4bef2b59 +0 -0
- data/spec/fixtures/project.git/objects/b9/2732c79e00cf2fad5ceceb61bbc7a9a9739d2c +0 -0
- data/spec/fixtures/project.git/objects/ba/01f5352549018dfdb95423b9e0974443ec0346 +1 -0
- data/spec/fixtures/project.git/objects/ba/8dd7d2162334426facd310f62ed923eb6f4cf5 +0 -0
- data/spec/fixtures/project.git/objects/bf/422763a0101a5971e15d53581acb825b4d38b8 +0 -0
- data/spec/fixtures/project.git/objects/c7/9ebd3ada1309bae694b8e6760592b1254e035c +2 -0
- data/spec/fixtures/project.git/objects/ca/7ced48ee61e517fc57576a33dc9d95f5d4f539 +2 -0
- data/spec/fixtures/project.git/objects/ce/eed5f059b6d374d08e0b1e732463842b3a5a91 +0 -0
- data/spec/fixtures/project.git/objects/d7/89fd591fee7d26c2f40c6614b5a91931377f88 +0 -0
- data/spec/fixtures/project.git/objects/d8/ec4dbb2804f53fd863cd0adb830c5bf4e85a1d +0 -0
- data/spec/fixtures/project.git/objects/dc/680e07f12748657d5a798495f08ce3ad884f0b +0 -0
- data/spec/fixtures/project.git/objects/de/de871ad5691708aca809557a823082a70dde69 +0 -0
- data/spec/fixtures/project.git/objects/e0/30730e56be52e4dc293c829f73e0c586073ad2 +0 -0
- data/spec/fixtures/project.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
- data/spec/fixtures/project.git/objects/e6/f7f8a92eb3177d10a809b96f41905e9be4cc2b +0 -0
- data/spec/fixtures/project.git/objects/ed/7115c4ae4c1885ee75f6ffe946b559546efd28 +0 -0
- data/spec/fixtures/project.git/objects/f1/e01dd1256054d5717ef0c89008f0a22639e273 +2 -0
- data/spec/fixtures/project.git/objects/f9/3e9c03a56aba331bcd98cb2ef00ba0003de285 +0 -0
- data/spec/fixtures/project.git/refs/heads/master +1 -0
- data/spec/fixtures/search.json +14159 -0
- data/spec/fixtures/search_results_1-50.html +1076 -0
- data/spec/fixtures/search_results_101-110.html +476 -0
- data/spec/fixtures/search_results_51-100.html +1076 -0
- data/spec/fixtures/translation_details.html +728 -0
- data/spec/fixtures/translation_details.json +738 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/strings_file_spec.rb +225 -0
- data/spec/translation_spec.rb +21 -0
- data/spec/translation_tool_spec.rb +146 -0
- data/trip_advisor.gemspec +38 -0
- metadata +474 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/tasklib'
|
|
3
|
+
|
|
4
|
+
module TripAdvisor
|
|
5
|
+
class TestDevice
|
|
6
|
+
attr_accessor :platform, :name, :valid_schemes
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@platform = 'iOS Simulator'
|
|
10
|
+
@name = 'iPhone 5s'
|
|
11
|
+
@valid_schemes = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def destination_arg(os_version)
|
|
15
|
+
"platform='#{platform}',OS=#{os_version},name='#{name}'"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def valid_for?(scheme_name)
|
|
19
|
+
valid_schemes.empty? || valid_schemes.include?(scheme_name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def sdk
|
|
23
|
+
platform.eql?('iOS Simulator') ? 'iphonesimulator' : 'iphoneos'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def short_name
|
|
27
|
+
"#{name.downcase.gsub(/\s+/, '')}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_s
|
|
31
|
+
"#{name} (#{platform})"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hash
|
|
35
|
+
[platform, name].hash
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def eql?(object)
|
|
39
|
+
platform.eql?(object.platform) && name.eql?(object.name)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class TestReport
|
|
44
|
+
include Singleton
|
|
45
|
+
|
|
46
|
+
def initialize
|
|
47
|
+
@namespaces = {}
|
|
48
|
+
@success = true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_result(namespace, ios_version, test_device, success)
|
|
52
|
+
@namespaces[namespace] ||= {}
|
|
53
|
+
@namespaces[namespace][ios_version] ||= {}
|
|
54
|
+
@namespaces[namespace][ios_version][test_device] = success
|
|
55
|
+
@success = false unless success
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def [](ios_version)
|
|
59
|
+
@namespaces[ios_version]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def success?
|
|
63
|
+
@success
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def failure?
|
|
67
|
+
@success == false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def report
|
|
71
|
+
@namespaces.each do |namespace, version_status|
|
|
72
|
+
version_status.each do |ios_version, device_status|
|
|
73
|
+
device_status.each do |test_device, success|
|
|
74
|
+
puts "\033[0;31m!! #{namespace} tests failed on #{test_device} under iOS #{ios_version}" unless success
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
puts "\033[0;32m** All tests executed successfully" if success?
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class TestTasks < Rake::TaskLib
|
|
83
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
|
84
|
+
|
|
85
|
+
attr_reader :namespace_name, :prepare_dependency
|
|
86
|
+
attr_accessor :workspace, :schemes_dir, :schemes, :ios_versions
|
|
87
|
+
attr_accessor :runner, :xctool_path, :xcodebuild_path, :environment_variables
|
|
88
|
+
attr_accessor :ios_versions, :test_devices, :settings
|
|
89
|
+
|
|
90
|
+
def initialize(namespace_name = :test)
|
|
91
|
+
@namespace_name = namespace_name.is_a?(Hash) ? namespace_name.keys.first : namespace_name
|
|
92
|
+
@prepare_dependency = namespace_name.is_a?(Hash) ? namespace_name.values.first : nil
|
|
93
|
+
@schemes_dir = 'Tests/Schemes'
|
|
94
|
+
@ios_versions = %w{7.0 8.0}
|
|
95
|
+
@xctool_path = '/usr/local/bin/xctool'
|
|
96
|
+
@xcodebuild_path = 'xcodebuild'
|
|
97
|
+
@runner = :xctool
|
|
98
|
+
@settings = {}
|
|
99
|
+
@environment_variables = ''
|
|
100
|
+
|
|
101
|
+
yield self if block_given?
|
|
102
|
+
raise "A workspace must be configured" unless workspace
|
|
103
|
+
raise "At least one scheme must be configured" unless schemes
|
|
104
|
+
raise "At least one test device must be configured" unless test_devices
|
|
105
|
+
define_tasks
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def runner=(runner)
|
|
109
|
+
raise ArgumentError, "Must be :xctool or :xcodebuild" unless %w{xctool xcodebuild}.include?(runner.to_s)
|
|
110
|
+
@runner = runner.to_sym
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def define_tasks
|
|
114
|
+
namespace self.namespace_name do
|
|
115
|
+
task (prepare_dependency ? { prepare: prepare_dependency} : :prepare ) do
|
|
116
|
+
run(%Q{mkdir -p "#{workspace}/xcshareddata/xcschemes" && cp Tests/Schemes/*.xcscheme "#{workspace}/xcshareddata/xcschemes/"})
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Iterate across the schemes and define a task for each version of iOS and an aggregate task
|
|
120
|
+
schemes.each do |scheme_namespace, scheme_names|
|
|
121
|
+
scheme_names_array = scheme_names.is_a?(Array) ? scheme_names : [scheme_names]
|
|
122
|
+
namespace scheme_namespace do
|
|
123
|
+
scheme_names_array.each do |scheme_name|
|
|
124
|
+
ios_versions.each do |ios_version|
|
|
125
|
+
namespace ios_version do
|
|
126
|
+
test_devices.each do |test_device|
|
|
127
|
+
next unless test_device.valid_for?(scheme_name)
|
|
128
|
+
desc "Run #{scheme_namespace} tests against #{ios_version} SDK on #{test_device}"
|
|
129
|
+
task test_device.short_name => :prepare do
|
|
130
|
+
test_scheme(scheme_name, namespace: scheme_namespace, ios_version: ios_version, test_device: test_device)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
desc "Run #{scheme_namespace} tests against #{ios_version} SDK on valid devices"
|
|
135
|
+
task "#{ios_version}" => test_devices.select { |test_device| test_device.valid_for?(scheme_name) }.map { |test_device| "#{scheme_namespace}:#{ios_version}:#{test_device.short_name}" }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
desc "Run #{scheme_namespace} tests against all SDKs and valid devices"
|
|
141
|
+
task scheme_namespace => ios_versions.map { |ios_version| "#{scheme_namespace}:#{ios_version}" }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
desc "Run the #{schemes.keys.join(', ')} tests on all devices and SDKs"
|
|
146
|
+
task namespace_name => schemes.keys.map { |scheme| "#{namespace_name}:#{scheme}" } do
|
|
147
|
+
TripAdvisor::TestReport.instance.report
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
def run(command)
|
|
153
|
+
puts "Executing `#{command}`"
|
|
154
|
+
system(command)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_scheme(scheme, options)
|
|
158
|
+
system(%q{killall "iPhone Simulator"})
|
|
159
|
+
namespace = options[:namespace]
|
|
160
|
+
ios_version = options[:ios_version]
|
|
161
|
+
test_device = options[:test_device]
|
|
162
|
+
test_sdk = "#{ios_version}"
|
|
163
|
+
|
|
164
|
+
settings_arg = settings.map { |k,v| "#{k}=#{v}"}.join(' ')
|
|
165
|
+
destination_arg = test_device.destination_arg(ios_version)
|
|
166
|
+
success = if xctool?
|
|
167
|
+
run("#{xctool_path} -workspace #{workspace} -scheme #{scheme} -sdk #{test_device.sdk} test -freshSimulator -destination #{destination_arg} #{settings_arg} #{environment_variables} DEVICE_NAME=#{test_device.short_name}_#{ios_version} -reporter junit:#{scheme}-#{test_sdk}-#{test_device.short_name}.xml")
|
|
168
|
+
elsif xcodebuild?
|
|
169
|
+
run("#{xcodebuild_path} -workspace #{workspace} -scheme #{scheme} -sdk #{test_device.sdk} test -destination #{destination_arg} #{settings_arg} #{environment_variables} DEVICE_NAME=#{test_device.short_name}_#{ios_version} | xcpretty -cr html --output build/reports/#{scheme}-#{test_sdk}-#{test_device.short_name}.html ; exit ${PIPESTATUS[0]}")
|
|
170
|
+
end
|
|
171
|
+
TripAdvisor::TestReport.instance.add_result(namespace, ios_version, test_device, success)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def xctool?
|
|
175
|
+
runner == :xctool
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def xcodebuild?
|
|
179
|
+
runner == :xcodebuild
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module TripAdvisor
|
|
2
|
+
class ResourceManager
|
|
3
|
+
attr_accessor :api_client, :output_dir
|
|
4
|
+
|
|
5
|
+
def initialize(options={})
|
|
6
|
+
options.each { |k,v| self.send("#{k}=", v) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def locales
|
|
10
|
+
@locales ||= api_client.get_flights_locales
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def dump_airports
|
|
14
|
+
provision_locale_projects
|
|
15
|
+
puts "Dumping Airport fixtures for #{locales.size} locales..."
|
|
16
|
+
locales.each do |locale|
|
|
17
|
+
locale_identifier = locale['locale']
|
|
18
|
+
begin
|
|
19
|
+
json = api_client.get(:airports, locale: locale_identifier)
|
|
20
|
+
rescue RestClient::Exception => ex
|
|
21
|
+
puts "ERROR: Failed Fetching Airports fixture for '#{locale_identifier}' locale_identifier: #{ex}"
|
|
22
|
+
next
|
|
23
|
+
end
|
|
24
|
+
language_code = TripAdvisor::Translation.locale_identifier_to_language_codes_mapping[locale_identifier] || locale_identifier
|
|
25
|
+
path = File.join(output_dir, "#{language_code}.lproj", "airports.json")
|
|
26
|
+
File.open(path, 'w') { |f| f << json }
|
|
27
|
+
puts " Wrote Airports fixture for '#{locale_identifier}' locale to #{path}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def dump_airlines
|
|
32
|
+
provision_locale_projects
|
|
33
|
+
puts "Dumping Airline fixtures for #{locales.size} locales..."
|
|
34
|
+
locales.each do |locale|
|
|
35
|
+
locale_identifier = locale['locale']
|
|
36
|
+
begin
|
|
37
|
+
json = api_client.get(:airlines, locale: locale_identifier)
|
|
38
|
+
rescue RestClient::Exception => ex
|
|
39
|
+
puts "ERROR: Failed Fetching Airlines fixture for '#{locale_identifier}' locale_identifier: #{ex}"
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
language_code = TripAdvisor::Translation.locale_identifier_to_language_codes_mapping[locale_identifier] || locale_identifier
|
|
43
|
+
path = File.join(output_dir, "#{language_code}.lproj", "airlines.json")
|
|
44
|
+
File.open(path, 'w') { |f| f << json }
|
|
45
|
+
puts " Wrote Airlines fixture for '#{locale_identifier}' locale to #{path}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def dump_purchase_link_providers
|
|
50
|
+
provision_locale_projects
|
|
51
|
+
puts "Dumping Purchase Link Provider fixtures for #{locales.size} locales..."
|
|
52
|
+
locales.each do |locale|
|
|
53
|
+
locale_identifier = locale['locale']
|
|
54
|
+
begin
|
|
55
|
+
json = api_client.get(:flight_purchase_link_providers, locale: locale_identifier)
|
|
56
|
+
rescue RestClient::Exception => ex
|
|
57
|
+
puts "ERROR: Failed Fetching Purchase Link Provider fixture for '#{locale_identifier}' locale_identifier: #{ex}"
|
|
58
|
+
next
|
|
59
|
+
end
|
|
60
|
+
language_code = TripAdvisor::Translation.locale_identifier_to_language_codes_mapping[locale_identifier] || locale_identifier
|
|
61
|
+
path = File.join(output_dir, "#{language_code}.lproj", "flight_purchase_link_providers.json")
|
|
62
|
+
File.open(path, 'w') { |f| f << json }
|
|
63
|
+
puts " Wrote Purchase Link Provider fixture for '#{locale_identifier}' locale to #{path}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def language_tags_from_locales_api
|
|
68
|
+
locales.map { |l| l['locale'] }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def provision_locale_projects(language_tags = self.language_tags_from_locales_api)
|
|
72
|
+
FileUtils.mkdir(output_dir) unless File.exists?(output_dir)
|
|
73
|
+
language_tags.inject([]) do |paths, language_tag|
|
|
74
|
+
language_tag = TripAdvisor::Translation.locale_identifier_to_language_codes_mapping[language_tag] || language_tag
|
|
75
|
+
path = File.join(output_dir, "#{language_tag}.lproj")
|
|
76
|
+
unless File.exist?(path)
|
|
77
|
+
FileUtils.mkdir(path)
|
|
78
|
+
paths << path
|
|
79
|
+
yield path if block_given?
|
|
80
|
+
end
|
|
81
|
+
paths
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def provision_localized_android_resources(language_tags = self.language_tags_from_locales_api)
|
|
86
|
+
FileUtils.mkdir(output_dir) unless File.exists?(output_dir)
|
|
87
|
+
language_tags.inject([]) do |paths, language_tag|
|
|
88
|
+
language_tag = TripAdvisor::Translation.locale_identifier_to_language_codes_mapping[language_tag] || language_tag
|
|
89
|
+
language_tag.gsub!(/-/,'_')
|
|
90
|
+
language_tag.gsub!(/_/,'-r')
|
|
91
|
+
path = File.join(output_dir, "values-#{language_tag}")
|
|
92
|
+
unless File.exist?(path)
|
|
93
|
+
FileUtils.mkdir(path)
|
|
94
|
+
paths << path
|
|
95
|
+
yield path if block_given?
|
|
96
|
+
end
|
|
97
|
+
paths
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require 'twine'
|
|
2
|
+
require 'twine/stringsfile'
|
|
3
|
+
|
|
4
|
+
module TripAdvisor
|
|
5
|
+
class StringsFile
|
|
6
|
+
attr_reader :path, :translations
|
|
7
|
+
|
|
8
|
+
def self.read(path)
|
|
9
|
+
self.new.tap { |sf| sf.read(path) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def merge_translations(translations)
|
|
13
|
+
translations.each do |translation|
|
|
14
|
+
if twine_row = twine_row_for_key(translation['en'].to_s)
|
|
15
|
+
update_twine_row_with_translation(twine_row, translation)
|
|
16
|
+
else
|
|
17
|
+
add_twine_row_for_translation(translation)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def merge_translations_android(translations)
|
|
23
|
+
translations.each do |translation|
|
|
24
|
+
if twine_row = twine_row_for_key(translation.key)
|
|
25
|
+
update_twine_row_with_translation(twine_row, translation)
|
|
26
|
+
else
|
|
27
|
+
add_twine_row_for_translation_android(translation)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def loaded?
|
|
33
|
+
@twine_strings_file != nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](key)
|
|
37
|
+
raise "Cannot access translations without reading a Strings file" unless loaded?
|
|
38
|
+
translations.detect { |translation| translation.key == key.to_s }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read(path)
|
|
42
|
+
@path = path
|
|
43
|
+
@twine_strings_file = Twine::StringsFile.new
|
|
44
|
+
@twine_strings_file.read(path)
|
|
45
|
+
load_translations_from_twine
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def write(path = self.path)
|
|
49
|
+
@twine_strings_file.write(path)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def language_tags
|
|
53
|
+
@twine_strings_file.language_codes
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
def load_translations_from_twine
|
|
58
|
+
@translations = @twine_strings_file.sections.collect do |section|
|
|
59
|
+
section.rows.map do |row|
|
|
60
|
+
Translation.new(key: row.key, note: row.comment).tap do |translation|
|
|
61
|
+
translation.localizations = row.translations.map do |locale_identifier, string|
|
|
62
|
+
Translation::Localization.new(locale_identifier: locale_identifier, string: string)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end.flatten
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# NOTE: For Android, the Twine files are keyed by Translation Tool key, _not_ English translation.
|
|
70
|
+
# NOTE: The Twine files are keyed by English translation, _not_ Translation Tool key
|
|
71
|
+
def twine_row_for_key(key)
|
|
72
|
+
@twine_strings_file.sections.each do |section|
|
|
73
|
+
if row = section.rows.detect { |row| row.key == key }
|
|
74
|
+
return row
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def update_twine_row_with_translation(twine_row, translation)
|
|
81
|
+
twine_row.comment = translation.note
|
|
82
|
+
translation.localizations.each do |localization|
|
|
83
|
+
@twine_strings_file.add_language_code(localization.locale_identifier)
|
|
84
|
+
twine_row.translations[localization.locale_identifier] = localization.string
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_twine_row_for_translation(translation)
|
|
89
|
+
# We merge new rows into the first section
|
|
90
|
+
section = @twine_strings_file.sections.first
|
|
91
|
+
english_string = translation['en']
|
|
92
|
+
raise "Cannot add Translation: No English ('en') localization available" unless english_string
|
|
93
|
+
row = Twine::StringsRow.new(english_string).tap do |row|
|
|
94
|
+
row.tags = ["key:#{translation.key}"]
|
|
95
|
+
update_twine_row_with_translation(row, translation)
|
|
96
|
+
end
|
|
97
|
+
section.rows << row
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def add_twine_row_for_translation_android(translation)
|
|
101
|
+
# We merge new rows into the first section
|
|
102
|
+
section = @twine_strings_file.sections.first
|
|
103
|
+
translation_tool_key = translation.key
|
|
104
|
+
raise "Cannot add Translation: No translation tool key." unless translation_tool_key
|
|
105
|
+
row = Twine::StringsRow.new(translation_tool_key).tap do |row|
|
|
106
|
+
row.tags = ["key:#{translation.key}"]
|
|
107
|
+
update_twine_row_with_translation(row, translation)
|
|
108
|
+
end
|
|
109
|
+
section.rows << row
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module TripAdvisor
|
|
2
|
+
class Translation
|
|
3
|
+
# Returns a dictionary that specifies how locale identifiers map to BCP 47 Language Identifiers
|
|
4
|
+
def self.locale_identifier_to_language_codes_mapping
|
|
5
|
+
{ "zh_TW" => "zh-Hant", "zh_CN"=> "zh-Hans", "zh_CN"=> "zh",
|
|
6
|
+
"no"=> "nb", "in"=> "id", "en_UK"=> "en-GB" }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Supports keyed lookup by locale identifier
|
|
10
|
+
class LocalizationsArray < Array
|
|
11
|
+
def [](locale_identifier)
|
|
12
|
+
if locale_identifier.is_a?(String)
|
|
13
|
+
self.detect { |localization| localization.locale_identifier == locale_identifier.to_s }
|
|
14
|
+
else
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Localization
|
|
21
|
+
attr_accessor :locale_identifier, :language_name, :string, :status
|
|
22
|
+
|
|
23
|
+
def initialize(attributes = {})
|
|
24
|
+
attributes.each { |k, v| self.send("#{k}=", v) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def locale_identifier=(locale_identifier)
|
|
28
|
+
@locale_identifier = (Translation.locale_identifier_to_language_codes_mapping[locale_identifier] || locale_identifier)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def string=(string)
|
|
32
|
+
@string = string.strip
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
string
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def active?
|
|
40
|
+
status == 'Active'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_accessor :id, :key, :note, :localizations
|
|
45
|
+
|
|
46
|
+
def initialize(attributes = {})
|
|
47
|
+
@localizations = LocalizationsArray.new
|
|
48
|
+
attributes.each { |k, v| self.send("#{k}=", v) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def key=(key)
|
|
52
|
+
@key = key.strip
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def localizations=(localizations)
|
|
56
|
+
@localizations.concat(localizations)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def [](locale_identifier)
|
|
60
|
+
localizations[locale_identifier]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'trip_advisor/translation'
|
|
2
|
+
require 'rest-client'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
require "active_support/core_ext"
|
|
5
|
+
|
|
6
|
+
module TripAdvisor
|
|
7
|
+
class TranslationTool
|
|
8
|
+
module Scraper
|
|
9
|
+
class << self
|
|
10
|
+
def translations_from_doc(doc)
|
|
11
|
+
Array.new.tap do |translations|
|
|
12
|
+
doc.each { |translation| translations << Translation.new(id: translation["id"], key: translation["key"], note: translation["note"]) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def localizations_from_doc(doc)
|
|
17
|
+
Array.new.tap do |localizations|
|
|
18
|
+
doc.each { |localization|
|
|
19
|
+
locale_string = localization["lang"]["locale"]
|
|
20
|
+
localized_string = localization["displayedValue"]
|
|
21
|
+
status = localization["isTranslatedValue"]
|
|
22
|
+
language_name = localization["lang"]["localizedDisplayName"]
|
|
23
|
+
locale_identifier = localization["lang"]["locale"]
|
|
24
|
+
localizations << Translation::Localization.new(locale_identifier: locale_identifier, language_name: language_name, string: localized_string, status: status, )
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ResultsPaginator
|
|
32
|
+
attr_reader :base_url, :offset, :total, :per_page, :params, :translations
|
|
33
|
+
|
|
34
|
+
def initialize(base_url, attributes = {})
|
|
35
|
+
@base_url = base_url
|
|
36
|
+
@total = 0
|
|
37
|
+
@per_page = 50
|
|
38
|
+
@offset = attributes[:offset] || 0
|
|
39
|
+
@params = attributes[:params]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load(options = {})
|
|
43
|
+
@offset = options[:offset] if options[:offset]
|
|
44
|
+
@offset = ((options[:page] - 1) * per_page) if options[:page]
|
|
45
|
+
raise ArgumentError, "Cannot load a negative offset" if @offset < 0
|
|
46
|
+
raise ArgumentError, "Cannot load an offset greater than the total number of objects" if total && @offset > total
|
|
47
|
+
response = RestClient.post(base_url, params.to_json, :content_type => :json, :accept => :json)
|
|
48
|
+
doc = JSON.parse(response)
|
|
49
|
+
@total = doc.count
|
|
50
|
+
@per_page = doc.count
|
|
51
|
+
@translations = Scraper.translations_from_doc(doc)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def page_count
|
|
55
|
+
(total / per_page.to_f).ceil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def loaded?
|
|
59
|
+
@translations
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def paginate
|
|
63
|
+
load unless loaded?
|
|
64
|
+
1.upto(page_count).collect do |page_number|
|
|
65
|
+
load(page: page_number).tap do |translations|
|
|
66
|
+
yield page_number, translations if block_given?
|
|
67
|
+
end
|
|
68
|
+
end.flatten
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias_method :all, :paginate
|
|
72
|
+
|
|
73
|
+
def current_page_index
|
|
74
|
+
offset / per_page
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def next_page
|
|
78
|
+
load(page: current_page_index + 1)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def previous_page
|
|
82
|
+
load(page: current_page_index - 1)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
attr_accessor :username, :password, :locale
|
|
87
|
+
|
|
88
|
+
def initialize(attributes = {})
|
|
89
|
+
attributes.each { |k, v| self.send("#{k}=", v) }
|
|
90
|
+
@locale ||= 'en'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def search(query, &block)
|
|
94
|
+
ResultsPaginator.new("https://#{username}:#{password}@localization.tripadvisor.com/translations/search/keys", params: { lang: locale, keyName: query }).tap do |paginator|
|
|
95
|
+
if block_given?
|
|
96
|
+
paginator.paginate(&block)
|
|
97
|
+
else
|
|
98
|
+
paginator.load
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def get_translation(translation)
|
|
104
|
+
translation = Translation.new(id: translation) if translation.is_a?(Integer)
|
|
105
|
+
response = RestClient.get("https://#{username}:#{password}@localization.tripadvisor.com/translations/keys/id/#{translation.id}/translations")
|
|
106
|
+
translation.localizations = Scraper.localizations_from_doc(JSON.parse(response))
|
|
107
|
+
translation
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/trip_advisor.rb
ADDED
data/run_tests.sh
ADDED
data/spec/api_spec.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'trip_advisor/api'
|
|
3
|
+
|
|
4
|
+
describe TripAdvisor::API do
|
|
5
|
+
describe '.get_locales' do
|
|
6
|
+
before(:each) do
|
|
7
|
+
@api = TripAdvisor::API.new(api_key: 'adf6d1b8-0aca-4b0c-a492-50530aadd7aa')
|
|
8
|
+
WebMock.allow_net_connect!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
after(:each) do
|
|
12
|
+
WebMock.disable_net_connect!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "load all locales from the API" do
|
|
16
|
+
@api.get_locales.count.should == 46
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '.get_flights_locales' do
|
|
21
|
+
before(:each) do
|
|
22
|
+
@api = TripAdvisor::API.new(api_key: 'adf6d1b8-0aca-4b0c-a492-50530aadd7aa')
|
|
23
|
+
WebMock.allow_net_connect!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
after(:each) do
|
|
27
|
+
WebMock.disable_net_connect!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "load all flights_enabled locales from the API" do
|
|
31
|
+
@api.get_flights_locales.count.should == 39
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|