terrestrial-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bin/terrestrial +44 -0
- data/circle.yml +11 -0
- data/lib/terrestrial/cli/android_xml_formatter.rb +43 -0
- data/lib/terrestrial/cli/android_xml_parser.rb +49 -0
- data/lib/terrestrial/cli/bootstrapper.rb +184 -0
- data/lib/terrestrial/cli/command.rb +20 -0
- data/lib/terrestrial/cli/detects_project_type.rb +16 -0
- data/lib/terrestrial/cli/dot_strings_formatter.rb +53 -0
- data/lib/terrestrial/cli/dot_strings_parser.rb +139 -0
- data/lib/terrestrial/cli/editor/android_xml.rb +64 -0
- data/lib/terrestrial/cli/editor/base_editor.rb +36 -0
- data/lib/terrestrial/cli/editor/objc.rb +66 -0
- data/lib/terrestrial/cli/editor/printer.rb +47 -0
- data/lib/terrestrial/cli/editor/storyboard.rb +98 -0
- data/lib/terrestrial/cli/editor/swift.rb +92 -0
- data/lib/terrestrial/cli/editor.rb +42 -0
- data/lib/terrestrial/cli/engine_mapper.rb +30 -0
- data/lib/terrestrial/cli/entry_collection_differ.rb +22 -0
- data/lib/terrestrial/cli/file_finder.rb +65 -0
- data/lib/terrestrial/cli/file_picker.rb +58 -0
- data/lib/terrestrial/cli/flight/ios_workflow.rb +81 -0
- data/lib/terrestrial/cli/flight/table_workflow.rb +77 -0
- data/lib/terrestrial/cli/flight.rb +93 -0
- data/lib/terrestrial/cli/ignite.rb +73 -0
- data/lib/terrestrial/cli/init.rb +133 -0
- data/lib/terrestrial/cli/mixpanel_client.rb +56 -0
- data/lib/terrestrial/cli/parser/android_xml.rb +82 -0
- data/lib/terrestrial/cli/parser/base_parser.rb +42 -0
- data/lib/terrestrial/cli/parser/objc.rb +127 -0
- data/lib/terrestrial/cli/parser/storyboard.rb +166 -0
- data/lib/terrestrial/cli/parser/string_analyser.rb +115 -0
- data/lib/terrestrial/cli/parser/swift.rb +102 -0
- data/lib/terrestrial/cli/parser.rb +25 -0
- data/lib/terrestrial/cli/photoshoot.rb +65 -0
- data/lib/terrestrial/cli/pull.rb +110 -0
- data/lib/terrestrial/cli/push.rb +40 -0
- data/lib/terrestrial/cli/scan.rb +72 -0
- data/lib/terrestrial/cli/string_registry.rb +30 -0
- data/lib/terrestrial/cli/terminal_ui.rb +25 -0
- data/lib/terrestrial/cli/variable_normalizer.rb +34 -0
- data/lib/terrestrial/cli/version.rb +5 -0
- data/lib/terrestrial/cli.rb +82 -0
- data/lib/terrestrial/config.rb +99 -0
- data/lib/terrestrial/creates_terrestrial_yml.rb +9 -0
- data/lib/terrestrial/web/response.rb +17 -0
- data/lib/terrestrial/web.rb +78 -0
- data/lib/terrestrial/yaml_helper.rb +48 -0
- data/lib/terrestrial.rb +7 -0
- data/terrestrial-cli.gemspec +29 -0
- metadata +188 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class FileFinder
|
4
|
+
|
5
|
+
EXCLUDED_FOLDERS = [
|
6
|
+
/Carthage\//,
|
7
|
+
/Pods\//,
|
8
|
+
/Tests\//
|
9
|
+
]
|
10
|
+
|
11
|
+
EXCLUDED_FILES = [
|
12
|
+
"LaunchScreen.storyboard"
|
13
|
+
]
|
14
|
+
|
15
|
+
def self.find(directory, extensions)
|
16
|
+
self.new(directory, extensions).find
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(directory, extensions)
|
20
|
+
@directory = directory
|
21
|
+
@extensions = extensions
|
22
|
+
end
|
23
|
+
|
24
|
+
def find
|
25
|
+
Dir[@directory + "/**/*.*"]
|
26
|
+
.map {|f| relative_path(f) }
|
27
|
+
.reject {|f| excluded_folders(f) }
|
28
|
+
.select {|f| @extensions.include?(File.extname(f)) }
|
29
|
+
.reject {|f| excluded_files(f) }
|
30
|
+
.select {|f| valid_paths(f) }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def relative_path(file)
|
36
|
+
Pathname.new(file)
|
37
|
+
.relative_path_from(Pathname.new(@directory))
|
38
|
+
.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid_paths(f)
|
42
|
+
# Some files need to be in specific places to count
|
43
|
+
# as "real files". For example, strings.xml files should
|
44
|
+
# only be read if they are in /res/values/strings.xml
|
45
|
+
#
|
46
|
+
# Add rules here as needed.
|
47
|
+
|
48
|
+
case File.extname(f)
|
49
|
+
when ".xml"
|
50
|
+
f.end_with? "/res/values/strings.xml"
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def excluded_folders(path)
|
57
|
+
EXCLUDED_FOLDERS.any? { |folder| path.scan(folder).any? }
|
58
|
+
end
|
59
|
+
|
60
|
+
def excluded_files(path)
|
61
|
+
EXCLUDED_FILES.any? { |name| File.basename(path) == name }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Terrestrial
|
4
|
+
module Cli
|
5
|
+
class FilePicker
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def run(files, platform)
|
9
|
+
if files.count == 1
|
10
|
+
single_file_workflow(files)
|
11
|
+
else
|
12
|
+
multiple_files_workflow(files)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def multiple_files_workflow(files)
|
17
|
+
puts "-- Terrestrial detected #{files.count} localization files:"
|
18
|
+
files.each_with_index do |path, i|
|
19
|
+
puts "(#{i + 1}) #{path}"
|
20
|
+
end
|
21
|
+
puts ""
|
22
|
+
puts "Select the files you want Terrestrial to track as the base localization: e.g. \"1,4,5\""
|
23
|
+
puts "(To not select any files, just hit return. You can edit tracked files in terrestrial.yml)"
|
24
|
+
|
25
|
+
result = STDIN.gets.chomp
|
26
|
+
|
27
|
+
process_result(result, files)
|
28
|
+
end
|
29
|
+
|
30
|
+
def single_file_workflow(files)
|
31
|
+
puts "Terrestrial detected #{files.count} file:"
|
32
|
+
puts "(1) #{files[0].to_s}"
|
33
|
+
puts ""
|
34
|
+
puts "Use this file as your base language file? (you can change this late in terrestrial.yml) y/n?"
|
35
|
+
|
36
|
+
result = STDIN.gets.chomp.strip
|
37
|
+
|
38
|
+
if result == "y"
|
39
|
+
files
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_result(result, files)
|
46
|
+
if result == ""
|
47
|
+
return []
|
48
|
+
else
|
49
|
+
result
|
50
|
+
.split(",")
|
51
|
+
.map {|i| i.to_i}
|
52
|
+
.map {|i| files[i - 1]}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Flight < Command
|
4
|
+
class IosWorkflow
|
5
|
+
|
6
|
+
def initialize(bootstrap_results)
|
7
|
+
@results = bootstrap_results
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
print_instructions
|
12
|
+
command = STDIN.gets.chomp
|
13
|
+
|
14
|
+
if command == "y"
|
15
|
+
lproj_folder = TerminalUI.show_spinner do
|
16
|
+
Editor.prepare_files(results.all_occurences)
|
17
|
+
initalize_localizable_strings_files
|
18
|
+
end
|
19
|
+
add_file_to_config(lproj_folder)
|
20
|
+
print_done_message(lproj_folder)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def initalize_localizable_strings_files
|
27
|
+
path = create_path_to_localization_files
|
28
|
+
|
29
|
+
File.open(path, "a+") do |f|
|
30
|
+
formatter = DotStringsFormatter.new(results)
|
31
|
+
|
32
|
+
f.write "// Created by Terrestrial (#{Time.now.to_s})"
|
33
|
+
f.write "\n\n"
|
34
|
+
f.write formatter.format
|
35
|
+
end
|
36
|
+
path
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_path_to_localization_files
|
40
|
+
folder_name = Pathname.new(Dir[Config[:directory] + "/*.xcodeproj"].first).basename(".*").to_s
|
41
|
+
base_lproj_path = FileUtils.mkdir_p(Config[:directory] + "/#{folder_name}" + "/Base.lproj").first
|
42
|
+
|
43
|
+
base_lproj_path + "/Localizable.strings"
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_file_to_config(path)
|
47
|
+
path_to_file = Pathname.new(path)
|
48
|
+
current_dir = Pathname.new(Config[:directory])
|
49
|
+
|
50
|
+
Config.load({ translation_files: [
|
51
|
+
path_to_file.relative_path_from(current_dir).to_s
|
52
|
+
]})
|
53
|
+
Config.update_project_config
|
54
|
+
end
|
55
|
+
|
56
|
+
def print_done_message(lproj_folder)
|
57
|
+
puts "------------------------------------"
|
58
|
+
puts "-- Done!"
|
59
|
+
puts "- Created Base.lproj in #{lproj_folder}."
|
60
|
+
puts "- Remember to include the new localization files in your project!"
|
61
|
+
end
|
62
|
+
|
63
|
+
def print_instructions
|
64
|
+
puts "- Terrestrial will add #{results.length} strings to your base Localizable.strings."
|
65
|
+
puts ""
|
66
|
+
puts "------------------------------------"
|
67
|
+
puts "-- Source Code"
|
68
|
+
puts "- Would you like Terrestrial to also modify the selected strings in your"
|
69
|
+
puts "- source code to call .translated?"
|
70
|
+
puts "- e.g. \"This is my string\" => \"This is my string\".translated"
|
71
|
+
puts ""
|
72
|
+
puts "y/n?"
|
73
|
+
end
|
74
|
+
|
75
|
+
def results
|
76
|
+
@results
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Flight < Command
|
4
|
+
class TableWorkflow
|
5
|
+
|
6
|
+
LOCAL_CONFIG = {
|
7
|
+
strings_per_page: 10
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(bootstrap_results)
|
11
|
+
@results = bootstrap_results
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
print_instructions
|
16
|
+
|
17
|
+
exclusions = []
|
18
|
+
i = 0
|
19
|
+
|
20
|
+
results.all_occurences.each_slice(LOCAL_CONFIG[:strings_per_page]).with_index do |five_strings, index|
|
21
|
+
puts "Page #{index + 1} of #{(results.all_occurences.count / LOCAL_CONFIG[:strings_per_page].to_f).ceil}"
|
22
|
+
|
23
|
+
table = create_string_table(five_strings, i)
|
24
|
+
i += LOCAL_CONFIG[:strings_per_page]
|
25
|
+
puts table
|
26
|
+
print_instructions
|
27
|
+
puts ""
|
28
|
+
|
29
|
+
command = STDIN.gets.chomp
|
30
|
+
if command == 'q'
|
31
|
+
abort "Aborting..."
|
32
|
+
else
|
33
|
+
begin
|
34
|
+
exclusions.concat(command.split(",").map(&:to_i))
|
35
|
+
rescue
|
36
|
+
abort "Couldn't process that command :( Aborting..."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
exclusions
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def create_string_table(strings, i)
|
46
|
+
Terminal::Table.new(headings: ['Index', 'String', 'File']) do |t|
|
47
|
+
strings.each_with_index do |string, tmp_index|
|
48
|
+
t.add_row([i, string.string, file_name_with_line_number(string)])
|
49
|
+
t.add_separator unless tmp_index == (strings.length - 1) || i == (strings.length - 1)
|
50
|
+
i += 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def print_instructions
|
56
|
+
puts "-- Instructions --"
|
57
|
+
puts "- To exclude any strings from translation, type the index of each string."
|
58
|
+
puts "- e.g. 1,2,4"
|
59
|
+
puts "------------------"
|
60
|
+
puts "Any Exclusions? (press return to continue or 'q' to quit at any time)"
|
61
|
+
end
|
62
|
+
|
63
|
+
def file_name_with_line_number(string)
|
64
|
+
if string.line_number
|
65
|
+
"#{string.file}:#{string.line_number}"
|
66
|
+
else
|
67
|
+
string.file
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def results
|
72
|
+
@results
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'terrestrial/cli/flight/ios_workflow.rb'
|
2
|
+
require 'terrestrial/cli/flight/table_workflow.rb'
|
3
|
+
require 'terminal-table'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Terrestrial
|
7
|
+
module Cli
|
8
|
+
class Flight < Command
|
9
|
+
|
10
|
+
def run
|
11
|
+
Config.load!
|
12
|
+
MixpanelClient.track("cli-flight-command")
|
13
|
+
|
14
|
+
if !Config.project_config_exist?
|
15
|
+
abort_not_initialized
|
16
|
+
end
|
17
|
+
if Config[:translation_files].any?
|
18
|
+
abort_already_run_flight
|
19
|
+
end
|
20
|
+
if Config[:platform] != "ios"
|
21
|
+
puts "'flight' is not supported on Android."
|
22
|
+
puts " iOS projects often just include strings in their source code instead of extracting them into resource files."
|
23
|
+
puts " We created 'flight' to get iOS project up and running quicker."
|
24
|
+
puts " 'R.string' makes localization much easier :)"
|
25
|
+
abort
|
26
|
+
end
|
27
|
+
|
28
|
+
puts "- Finding untranslated human readable strings..."
|
29
|
+
TerminalUI.show_spinner do
|
30
|
+
find_new_strings
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "------------------------------------"
|
34
|
+
puts "- Found #{strings.count} strings"
|
35
|
+
puts ""
|
36
|
+
exclusions = TableWorkflow.new(strings).run
|
37
|
+
puts "------------------------------------"
|
38
|
+
puts "- Done!"
|
39
|
+
|
40
|
+
strings.exclude_occurences(exclusions)
|
41
|
+
|
42
|
+
if Config[:platform] == "ios"
|
43
|
+
IosWorkflow.new(strings).run
|
44
|
+
elsif Config[:platform] == 'android'
|
45
|
+
android_workflow
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def android_workflow
|
52
|
+
puts "- Terrestrial will annotate the selected strings in your strings.xml file:"
|
53
|
+
puts "- e.g. <string name='my_name'>My string!</string> => <string terrestrial='true' name='my_name'>My string</string>"
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_new_strings
|
57
|
+
@strings = Bootstrapper.find_new_strings(Config[:directory])
|
58
|
+
end
|
59
|
+
|
60
|
+
def strings
|
61
|
+
@strings
|
62
|
+
end
|
63
|
+
|
64
|
+
def abort_not_initialized
|
65
|
+
puts "You should initialize your project before running flight."
|
66
|
+
puts "It's simple! You can do this by running:"
|
67
|
+
puts ""
|
68
|
+
puts " terrestrial init --api-key <API KEY> --project-id <PROJECT ID>"
|
69
|
+
puts ""
|
70
|
+
puts "You can find your Api Key and Project ID at https://mission.terrestrial.io"
|
71
|
+
abort
|
72
|
+
end
|
73
|
+
|
74
|
+
def abort_already_run_flight
|
75
|
+
if Config[:platform] == "ios"
|
76
|
+
puts "Looks like you already have Localizable.strings files."
|
77
|
+
puts "'flight' scans your source code for human readable strings that have not been translated"
|
78
|
+
puts "and helps you quickstart your internaionalization process."
|
79
|
+
puts ""
|
80
|
+
puts "If you want to new strings into your .strings file, run 'terrestrial gen'. It will:"
|
81
|
+
puts " 1. Scan your source code for .translated and NSLocalizedString calls."
|
82
|
+
puts " 2. Determine if the strings already exist in Localizable.strings."
|
83
|
+
puts " 3. Append any new strings to your base Localizable.strings."
|
84
|
+
puts ""
|
85
|
+
puts "For more information, visit http://docs.terrestrial.io/, or jump on our Slack via https://terrestrial-slack.herokuapp.com/"
|
86
|
+
abort
|
87
|
+
else
|
88
|
+
# TODO
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Ignite < Command
|
4
|
+
|
5
|
+
WORKING_DIR = '/usr/local/var/terrestrial'
|
6
|
+
|
7
|
+
def run
|
8
|
+
Config.load!
|
9
|
+
MixpanelClient.track("cli-ignite-command")
|
10
|
+
lang = opts["language"]
|
11
|
+
|
12
|
+
if Config[:platform] != "ios"
|
13
|
+
abort "Unfortunately launching your app in a locale via 'ignite' is only supported on iOS at this time."
|
14
|
+
end
|
15
|
+
|
16
|
+
if lang.nil? || lang.empty?
|
17
|
+
abort "Please provide a locale to launch the simulator in.\n e.g. 'terrestrial ignite es'"
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Starting simulator in locale \"#{lang}\"..."
|
21
|
+
|
22
|
+
TerminalUI.show_spinner do
|
23
|
+
ensure_var_folder_exists
|
24
|
+
|
25
|
+
workspace = Dir["#{Config[:directory]}/*.xcworkspace"][0]
|
26
|
+
project = Dir["#{Config[:directory]}/*.xcodeproj"][0]
|
27
|
+
|
28
|
+
# Kill simulator and
|
29
|
+
system("killall \"Simulator\" &> /dev/null")
|
30
|
+
`rm -rf #{WORKING_DIR}`
|
31
|
+
|
32
|
+
if workspace
|
33
|
+
# If a workspace exists we want to build it instead of the project.
|
34
|
+
# We assume the scheme we want to use is simply the application name
|
35
|
+
app_name = File.basename(workspace).split(".").first
|
36
|
+
`xcodebuild -workspace "#{workspace}" -arch "i386" ONLY_ACTIVE_ARCH=NO VALID_ARCHS="i386 x86_64" -scheme #{app_name} -sdk iphonesimulator clean`
|
37
|
+
`xcodebuild -workspace "#{workspace}" -arch "i386" ONLY_ACTIVE_ARCH=NO VALID_ARCHS="i386 x86_64" -scheme #{app_name} -sdk iphonesimulator CONFIGURATION_BUILD_DIR=#{WORKING_DIR}`
|
38
|
+
else
|
39
|
+
app_name = File.basename(project).split(".").first
|
40
|
+
`xcodebuild -project "#{project}" -arch i386 -sdk iphonesimulator clean`
|
41
|
+
`xcodebuild -project "#{project}" -arch i386 -sdk iphonesimulator CONFIGURATION_BUILD_DIR=#{WORKING_DIR}`
|
42
|
+
end
|
43
|
+
`open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app`
|
44
|
+
|
45
|
+
# Here we literally sleep until the Simulator has been booted
|
46
|
+
wait_until_booted = %{
|
47
|
+
count=`xcrun simctl list | grep Booted | wc -l | sed -e 's/ //g'`
|
48
|
+
while [ $count -lt 1 ]
|
49
|
+
do
|
50
|
+
sleep 1
|
51
|
+
count=`xcrun simctl list | grep Booted | wc -l | sed -e 's/ //g'`
|
52
|
+
done
|
53
|
+
}
|
54
|
+
`#{wait_until_booted}`
|
55
|
+
|
56
|
+
# Here we magically find the bundle identifier of the app
|
57
|
+
command = "defaults read \"#{Dir[WORKING_DIR + '/*.app/Info.plist'].first}\" CFBundleIdentifier"
|
58
|
+
bundle_name = `#{command}`.chop
|
59
|
+
|
60
|
+
# Reinstall the app,
|
61
|
+
# Run it with the locale we want
|
62
|
+
`xcrun simctl uninstall booted #{bundle_name}`
|
63
|
+
`xcrun simctl install booted "#{Dir[WORKING_DIR + "/" + app_name + ".app"].first}"`
|
64
|
+
`xcrun simctl launch booted #{bundle_name} --args -AppleLanguages \\(#{lang}\\)`
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def ensure_var_folder_exists
|
69
|
+
`mkdir -p #{WORKING_DIR}`
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Init < Command
|
4
|
+
|
5
|
+
def run
|
6
|
+
# Fail early if project already exists
|
7
|
+
Config.load!({}, project: false)
|
8
|
+
MixpanelClient.track("cli-init-command")
|
9
|
+
|
10
|
+
if Config.project_config_exist?
|
11
|
+
abort "Looks like there already exists a project in this directory. Are you in the correct folder?"
|
12
|
+
end
|
13
|
+
|
14
|
+
check_arguments
|
15
|
+
detect_platform
|
16
|
+
|
17
|
+
puts "-- Terrestrial Initializing"
|
18
|
+
puts "Adding new app! Searching for localization files..."
|
19
|
+
|
20
|
+
TerminalUI.show_spinner do
|
21
|
+
# Otherwise the whole process is too quick for the eye
|
22
|
+
sleep 2 unless Config.testing?
|
23
|
+
end
|
24
|
+
puts ""
|
25
|
+
|
26
|
+
select_translation_files
|
27
|
+
create_app_in_web
|
28
|
+
|
29
|
+
if @response.success?
|
30
|
+
update_config
|
31
|
+
|
32
|
+
puts "-- Success!"
|
33
|
+
puts "App platform added to project! You can view your app at https://mission.terrestrial.io/projects/#{Config[:project_id]}/apps/#{Config[:app_id]}"
|
34
|
+
puts ""
|
35
|
+
puts "-- What to do next?"
|
36
|
+
|
37
|
+
if @translation_files.any?
|
38
|
+
puts "Run 'terrestrial scan' to see which strings Terrestrial is currently tracking."
|
39
|
+
puts "When you're ready to upload your strings for translation, run 'terrestrial push'!"
|
40
|
+
elsif @translation_files.none? && @platform == "ios"
|
41
|
+
puts "To get started localizing your app, run 'terrestrial flight'."
|
42
|
+
puts "Terrestrial will scan your code for strings, and generate the necessary localization files."
|
43
|
+
elsif @translation_files.none? && @platform == "android"
|
44
|
+
puts "Looks like Terrestrial does not know which strings.xml files to track."
|
45
|
+
puts "To continue, add your base language strings.xml file to terrestrial.yml."
|
46
|
+
puts "When you're ready, run 'terrestrial scan' to see which strings Terrestrial is tracking, and 'terrestrial push' to upload."
|
47
|
+
end
|
48
|
+
puts ""
|
49
|
+
puts "For more information, see http://docs.terrestrial.io or jump on Slack at https://terrestrial-slack.herokuapp.com/ if you have any questions."
|
50
|
+
else
|
51
|
+
puts "Oh snap. There was an error initializing your project."
|
52
|
+
puts response.body.inspect
|
53
|
+
abort
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def select_translation_files
|
60
|
+
@tranlation_files = []
|
61
|
+
|
62
|
+
files = find_platform_translation_files
|
63
|
+
if files.any?
|
64
|
+
@tranlation_files = FilePicker.run(files, @platform)
|
65
|
+
|
66
|
+
if @tranlation_files.count == 1
|
67
|
+
puts "Tracking #{@tranlation_files.count} file!"
|
68
|
+
else
|
69
|
+
puts "Tracking #{@tranlation_files.count} files!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_platform_translation_files
|
75
|
+
if @platform == "ios"
|
76
|
+
Dir[Config[:directory] + "/**/*.strings"].map {|f| relative_path(f) }
|
77
|
+
elsif @platform == "android"
|
78
|
+
Dir[Config[:directory] + "/**/*/res/values/strings.xml"].map {|f| relative_path(f) }
|
79
|
+
else
|
80
|
+
raise "Unknown platform #{@platform}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_config
|
85
|
+
Terrestrial::Config.load({
|
86
|
+
app_id: @response.body["data"]["id"],
|
87
|
+
project_id: @project_id,
|
88
|
+
platform: @platform,
|
89
|
+
api_key: @api_key,
|
90
|
+
translation_files: @tranlation_files
|
91
|
+
})
|
92
|
+
|
93
|
+
Terrestrial::Config.update_global_config
|
94
|
+
Terrestrial::Config.update_project_config
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_app_in_web
|
98
|
+
@client = Terrestrial::Web.new(@api_key)
|
99
|
+
@response = @client.create_app(@project_id, @platform)
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_arguments
|
103
|
+
@api_key = opts[:api_key] || Config[:api_key] ||
|
104
|
+
abort("No api key provided. You can find your API key at https://mission.terrestrial.io/.")
|
105
|
+
|
106
|
+
@project_id = opts.fetch(:project_id) { abort(
|
107
|
+
"No project ID provided. Terrestrial needs to know which project this app belongs to.\n" +
|
108
|
+
"Visit https://mission.terrestrial.io to find your project ID."
|
109
|
+
)}
|
110
|
+
end
|
111
|
+
|
112
|
+
def detect_platform
|
113
|
+
@platform = DetectsProjectType.run
|
114
|
+
end
|
115
|
+
|
116
|
+
def project_id
|
117
|
+
@project_id
|
118
|
+
end
|
119
|
+
|
120
|
+
def api_key
|
121
|
+
@api_key
|
122
|
+
end
|
123
|
+
|
124
|
+
def relative_path(file)
|
125
|
+
current_dir = Pathname.new(Config[:directory])
|
126
|
+
|
127
|
+
Pathname.new(file)
|
128
|
+
.relative_path_from(current_dir)
|
129
|
+
.to_s
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Terrestrial
|
5
|
+
module Cli
|
6
|
+
module MixpanelClient
|
7
|
+
class << self
|
8
|
+
|
9
|
+
TOKEN = "47d6a27568a3c842ead24b14907eb04e"
|
10
|
+
URL = "https://api.mixpanel.com/track"
|
11
|
+
|
12
|
+
def track(event)
|
13
|
+
# If we're live
|
14
|
+
if Config[:api_url] == "https://mission.terrestrial.io"
|
15
|
+
`curl -silent -X POST #{URL}?data=#{format_event(event)} &`
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def user_identifier
|
20
|
+
if Config[:user_id]
|
21
|
+
Config[:user_id]
|
22
|
+
else
|
23
|
+
fetch_and_save_user_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_event(event)
|
28
|
+
Base64.strict_encode64(event_json(event))
|
29
|
+
end
|
30
|
+
|
31
|
+
def event_json(event)
|
32
|
+
{
|
33
|
+
event: event,
|
34
|
+
properties: {
|
35
|
+
distinct_id: user_identifier,
|
36
|
+
token: TOKEN,
|
37
|
+
time: Time.now.to_i
|
38
|
+
}
|
39
|
+
}.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch_and_save_user_id
|
43
|
+
response = Web.new.get_profile
|
44
|
+
if response.success?
|
45
|
+
id = response.body["data"]["user"]["id"]
|
46
|
+
Config.load({:user_id => id})
|
47
|
+
Config.update_global_config
|
48
|
+
id
|
49
|
+
else
|
50
|
+
"unknown"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|