trip_advisor 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|