bin/crowdin-cli in crowdin-cli-0.4.1 vs bin/crowdin-cli in crowdin-cli-0.4.2
- old
+ new
@@ -74,10 +74,11 @@
'%three_letters_code%' => lang['iso_639_3'],
'%locale%' => lang['locale'],
'%locale_with_underscore%' => lang['locale'].gsub('-', '_'),
'%android_code%' => android_locale_code(lang['locale']),
'%osx_code%' => osx_language_code(lang['crowdin_code']) + '.lproj',
+ '%osx_xliff%' => osx_language_code(lang['crowdin_code']) + '.xliff',
}
placeholders = pattern.inject([]){ |memo, h| memo << h.first[/%(.*)%/, 1] }
unless languages_mapping.nil?
@@ -201,10 +202,11 @@
'%three_letters_code%',
'%locale%',
'%locale_with_underscore%',
'%android_code%',
'%osx_code%',
+ '%osx_liff%',
'%original_file_name%',
'%original_path%',
'%file_extension%',
'%file_name%',
]
@@ -233,22 +235,22 @@
end
end
# Extract compressed files +files_list+ in a ZIP archive +zipfile_name+ to +dest_path+
#
-# +files_list+ is a Hash of key-value pairs. Where key is a posible archive filename based on current project configuration
+# +files_list+ is a Hash of key-value pairs. Where key is a possible archive filename based on current project configuration
# and value is the expanded filename
#
def unzip_file_with_translations(zipfile_name, dest_path, files_list)
# overwrite files if they already exist inside of the extracted path
Zip.on_exists_proc = true
# files that exists in archive and doesn't match current project configuration
unmatched_files = []
Zip::File.open(zipfile_name) do |zipfile|
- zipfile.select{ |zip_entry| zip_entry.file? }.each do |f|
+ zipfile.select { |zip_entry| zip_entry.file? }.each do |f|
# `f' - relative path in archive
file = files_list[f.name]
if file
fpath = File.join(dest_path, file)
FileUtils.mkdir_p(File.dirname(fpath))
@@ -314,10 +316,17 @@
display_tree(val, level, branches)
end
end
end
+class String
+ def strip_heredoc
+ indent = scan(/^[ \t]*(?=\S)/).min.size || 0
+ gsub(/^[ \t]{#{indent}}/, '')
+ end
+end
+
###
include GLI::App
version Crowdin::CLI::VERSION
@@ -327,11 +336,11 @@
program_long_desc I18n.t('app.long_desc')
sort_help :manually # help commands are ordered in the order declared
wrap_help_text :to_terminal
desc I18n.t('app.switches.verbose.desc')
-switch [:v, :verbose], :negatable => false
+switch [:v, :verbose], negatable: false
desc I18n.t('app.flags.config.desc')
default_value File.join(Dir.pwd, 'crowdin.yaml')
arg_name '<s>'
flag [:c, :config]
@@ -387,11 +396,11 @@
end
local_files << local_file
else
Find.find(@base_path) do |source_path|
- dest = source_path.sub(/\A#{@base_path}/, '') # relative path in Crowdin
+ dest = source_path.sub(/\A#{Regexp.escape(@base_path)}/, '') # relative path in Crowdin
if File.directory?(source_path)
if ignores.any? { |pattern| File.fnmatch?(pattern, dest, File::FNM_PATHNAME) }
Find.prune # Don't look any further into this directory
else
@@ -416,15 +425,15 @@
end # if File.exists?
end # @config['files']
if dest_files.empty?
- exit_now! <<EOS
-No source files to upload.
-Check your configuration file to ensure that they contain valid directives.
-See http://crowdin.com/page/cli-tool#configuration-file for more details.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ No source files to upload.
+ Check your configuration file to ensure that they contain valid directives.
+ See http://crowdin.com/page/cli-tool#configuration-file for more details.
+ EOS
end
common_dir = @preserve_hierarchy ? '' : find_common_directory_path(dest_files)
local_project_tree = get_local_files_hierarchy(local_files.collect { |h| h[:dest].sub(common_dir, '') })
@@ -433,11 +442,11 @@
# Create directory tree
#
create_dirs = local_project_tree[:dirs] - remote_project_tree[:dirs]
create_dirs.each do |dir|
- puts "Create directory `#{dir}`"
+ puts "Creating directory `#{dir}`"
@crowdin.add_directory(dir)
end
if options['auto-update']
# Update existing files in Crowdin project
@@ -522,11 +531,11 @@
# do nothing
else
if project_languages.include?(language)
project_languages = [] << language
else
- exit_now!("language '#{language}' doesn't exist in a project")
+ exit_now!("language '#{language}' doesn't exist in the project")
end
end
supported_languages = @crowdin.supported_languages
@@ -555,20 +564,21 @@
else
file_translation_languages = translation_languages
end
if File.exists?(File.join(@base_path, file['source']))
- dest = file['source'].sub(/\A#{@base_path}/, '')
+ dest = file['dest'] || file['source']
+ dest.sub!(/\A#{Regexp.escape(@base_path)}/, '')
dest_files << dest
file_translation_languages.each do |lang|
source = export_pattern_to_path(dest, file['translation'], lang, languages_mapping)
translated_files[lang['crowdin_code']] << { source: File.join(@base_path, source), dest: dest }
end
else
Find.find(@base_path) do |source_path|
- dest = source_path.sub(/\A#{@base_path}/, '') # relative path in Crowdin
+ dest = source_path.sub(/\A#{Regexp.escape(@base_path)}/, '') # relative path in Crowdin
if File.directory?(source_path)
if ignores.any? { |pattern| File.fnmatch?(pattern, dest, File::FNM_PATHNAME) }
Find.prune # Don't look any further into this directory
else
@@ -591,42 +601,42 @@
end # if
end # @config['files']
if dest_files.empty?
- exit_now! <<EOS
-No translation files to upload.
-Check your configuration file to ensure that they contain valid directives.
-See http://crowdin.com/page/cli-tool#configuration-file for more details.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ No translation files to upload.
+ Check your configuration file to ensure that they contain valid directives.
+ See https://crowdin.com/page/cli-tool#configuration-file for more details.
+ EOS
end
common_dir = @preserve_hierarchy ? '' : find_common_directory_path(dest_files)
translated_files.each_pair do |language, files|
files.each do |file|
file[:dest] = file[:dest].sub(common_dir, '')
if remote_project_tree[:files].include?(file[:dest])
if File.exist?(file[:source])
- print "Uploading translation file `#{file[:source].sub(/\A#{@base_path}/, '')}'"
+ print "Uploading translation file `#{file[:source].sub(/\A#{Regexp.escape(@base_path)}/, '')}'"
resp = @crowdin.upload_translation([] << file, language, params)
case resp['files'].first[1]
when 'skipped'
- puts "\rUploading translation file `#{file[:source].sub(/\A#{@base_path}/, '')}' - Skipped"
+ puts "\rUploading translation file `#{file[:source].sub(/\A#{Regexp.escape(@base_path)}/, '')}' - Skipped"
when 'uploaded'
- puts "\rUploading translation file `#{file[:source].sub(/\A#{@base_path}/, '')}' - OK"
+ puts "\rUploading translation file `#{file[:source].sub(/\A#{Regexp.escape(@base_path)}/, '')}' - OK"
when 'not_allowed'
- puts "\rUploading translation file `#{file[:source].sub(/\A#{@base_path}/, '')}' - is not possible"
+ puts "\rUploading translation file `#{file[:source].sub(/\A#{Regexp.escape(@base_path)}/, '')}' - is not possible"
end
else
puts "Warning: Local file `#{file[:source]}' does not exist"
end
else
# if source file does not exist, don't upload translations
- puts "Warning: Skip `#{file[:source].sub(/\A#{@base_path}/, '')}'"
+ puts "Warning: Skip `#{file[:source].sub(/\A#{Regexp.escape(@base_path)}/, '')}'. Translation can not be uploaded for a non-existent source file `#{file[:dest]}'. Please upload sources first."
end
end
end
end # action
@@ -639,11 +649,11 @@
command :download do |c|
c.desc I18n.t('app.commands.download.flags.language.desc')
c.long_desc I18n.t('app.commands.download.flags.language.long_desc')
c.arg_name 'language_code'
- c.flag [:l, :language], :default_value => 'all'
+ c.flag [:l, :language], default_value: 'all'
c.action do |global_options ,options, args|
language = options[:language]
supported_languages = @crowdin.supported_languages
@@ -667,11 +677,11 @@
exit_now!("language '#{language}' doesn't exist in a project")
end
end
# use export API method before to download the most recent translations
- print 'Build ZIP archive with the latest translations '
+ print 'Building ZIP archive with the latest translations '
export_translations = @crowdin.export_translations
if export_translations['success']
if export_translations['success']['status'] == 'built'
puts "- OK"
elsif export_translations['success']['status'] == 'skipped'
@@ -702,22 +712,22 @@
else
file_translation_languages = translation_languages
end
if File.exists?(File.join(@base_path, file['source']))
- dest = file['source'].sub(/\A#{@base_path}/, '')
+ dest = file['source'].sub(/\A#{Regexp.escape(@base_path)}/, '')
file_translation_languages.each do |lang|
zipped_file = export_pattern_to_path(dest, file['translation'], lang)
zipped_file.sub!(/^\//, '')
local_file = export_pattern_to_path(dest, file['translation'], lang, languages_mapping)
downloadable_files_hash[zipped_file] = local_file
end
else
Find.find(@base_path) do |source_path|
- dest = source_path.sub(/\A#{@base_path}/, '') # relative path in Crowdin
+ dest = source_path.sub(/\A#{Regexp.escape(@base_path)}/, '') # relative path in Crowdin
if File.directory?(source_path)
if ignores.any? { |pattern| File.fnmatch?(pattern, dest, File::FNM_PATHNAME) }
Find.prune # Don't look any further into this directory
else
@@ -760,11 +770,11 @@
command :list do |ls_cmd|
ls_cmd.desc I18n.t('app.commands.list.commands.project.desc')
ls_cmd.command :project do |proj_cmd|
proj_cmd.desc I18n.t('app.commands.list.switches.tree.desc')
- proj_cmd.switch ['tree'], :negatable => false
+ proj_cmd.switch ['tree'], negatable: false
proj_cmd.action do |global_options, options, args|
project_info = @crowdin.project_info
remote_project_tree = get_remote_files_hierarchy(project_info['files'])
@@ -778,11 +788,11 @@
end
ls_cmd.desc I18n.t('app.commands.list.commands.sources.desc')
ls_cmd.command :sources do |src_cmd|
src_cmd.desc I18n.t('app.commands.list.switches.tree.desc')
- src_cmd.switch ['tree'], :negatable => false
+ src_cmd.switch ['tree'], negatable: false
src_cmd.action do |global_options, options, args|
local_files = []
dest_files = []
@@ -804,11 +814,11 @@
end
local_files << local_file
else
Find.find(@base_path) do |source_path|
- dest = source_path.sub(/\A#{@base_path}/, '') # relative path in Crowdin
+ dest = source_path.sub(/\A#{Regexp.escape(@base_path)}/, '') # relative path in Crowdin
if File.directory?(source_path)
if ignores.any? { |pattern| File.fnmatch?(pattern, dest, File::FNM_PATHNAME) }
Find.prune # Don't look any further into this directory
else
@@ -848,11 +858,11 @@
end # list sources
ls_cmd.desc I18n.t('app.commands.list.commands.translations.desc')
ls_cmd.command :translations do |trans_cmd|
trans_cmd.desc I18n.t('app.commands.list.switches.tree.desc')
- trans_cmd.switch ['tree'], :negatable => false
+ trans_cmd.switch ['tree'], negatable: false
trans_cmd.action do |global_options, options, args|
project_info = @crowdin.project_info
project_languages = project_info['languages'].collect{ |h| h['code'] }
@@ -865,20 +875,20 @@
languages_mapping = file['languages_mapping'] # Hash or NilClass
ignores = file['ignore'] || []
if File.exists?(File.join(@base_path, file['source']))
- dest = file['source'].sub(/\A#{@base_path}/, '')
+ dest = file['source'].sub(/\A#{Regexp.escape(@base_path)}/, '')
translation_languages.each do |lang|
local_file = export_pattern_to_path(dest, file['translation'], lang, languages_mapping)
translation_files << local_file
end
else
Find.find(@base_path) do |source_path|
- dest = source_path.sub(/\A#{@base_path}/, '') # relative path in Crowdin
+ dest = source_path.sub(/\A#{Regexp.escape(@base_path)}/, '') # relative path in Crowdin
if File.directory?(source_path)
if ignores.any? { |pattern| File.fnmatch?(pattern, dest, File::FNM_PATHNAME) }
Find.prune # Don't look any further into this directory
else
@@ -933,99 +943,136 @@
'content_segmentation',
'translatable_elements',
]
unless File.exists?(globals[:config])
- exit_now! <<EOS
-Can't find configuration file (default `crowdin.yaml').
-Type `crowdin-cli help` to know how to specify custom configuration file
+ exit_now! <<-EOS.strip_heredoc
+ Can't find configuration file (default `crowdin.yaml').
+ Type `crowdin-cli help` to know how to specify custom configuration file
-See http://crowdin.com/page/cli-tool#configuration-file for more details
-EOS
- else
- begin
- @config = YAML.load_file(globals[:config]) || {}
- rescue Psych::SyntaxError => err
- exit_now! <<EOS
-Could not parse YAML: #{err.message}
+ See http://crowdin.com/page/cli-tool#configuration-file for more details
+ EOS
+ end
-We were unable to successfully parse the crowdin.yaml file that you provided - it most likely is not well-formed YAML.
-Please check whether your crowdin.yaml is valid YAML - you can use the http://yamllint.com/ validator to do this - and make any necessary changes to fix it.
-EOS
- end
+ # load project-specific configuration
+ #
+ begin
+ # undocumented feature
+ # you can use ERB in your config file, e.g.
+ # api_key: <%= # ruby code ... %>
+ #
+ @config = YAML.load(ERB.new(File.read(globals[:config])).result) || {}
+ rescue Psych::SyntaxError => err
+ exit_now! <<-EOS.strip_heredoc
+ Could not parse YAML: #{err.message}
- if File.exists?(globals[:identity])
- identity = YAML.load_file(globals[:identity]) || {}
- ['api_key', 'project_identifier', 'base_path'].each do |key|
- @config[key] = identity[key] if identity[key]
+ We were unable to successfully parse the crowdin.yaml file that you provided - most likely it is not well-formatted YAML.
+ Please check whether your crowdin.yaml is valid YAML - you can use the http://yamllint.com/ validator to do this - and make any necessary changes to fix it.
+ EOS
+ end
+
+ # try to load the API credentials from an environment variable, e.g.
+ #
+ # project_identifier_env: 'CROWDIN_PROJECT_ID'
+ # api_key_env: 'CROWDIN_API_KEY'
+ #
+ ['api_key', 'project_identifier'].each do |key|
+ if @config["#{key}_env"]
+ unless @config[key] # project credentials have a higher priority if they are specified in the config
+ @config[key] = ENV[@config["#{key}_env"]]
end
end
+ end
- ['api_key', 'project_identifier'].each do |key|
- unless @config[key]
- exit_now! <<EOS
-Configuration file misses required option `#{key}`
-
-See http://crowdin.com/page/cli-tool#configuration-file for more details
-EOS
- end
+ # user credentials in the user-specific config file have a higher priority than project-specific
+ #
+ if File.exists?(globals[:identity])
+ identity = YAML.load(ERB.new(File.read(globals[:identity])).result) || {}
+ ['api_key', 'project_identifier', 'base_path'].each do |key|
+ @config[key] = identity[key] if identity[key]
end
+ end
- unless @config['files']
- exit_now! <<EOS
-Configuration file misses required section `files`
+ ['api_key', 'project_identifier'].each do |key|
+ unless @config[key]
+ exit_now! <<-EOS.strip_heredoc
+ Configuration file misses required option `#{key}`
-See http://crowdin.com/page/cli-tool#configuration-file for more details
-EOS
+ See http://crowdin.com/page/cli-tool#configuration-file for more details
+ EOS
end
end
+ unless @config['files']
+ exit_now! <<-EOS.strip_heredoc
+ Configuration file misses required section `files`
+
+ See https://crowdin.com/page/cli-tool#configuration-file for more details
+ EOS
+ end
+
@config['files'].each do |file|
+ unless file['source']
+ exit_now! <<-EOS.strip_heredoc
+ Files section misses required parameter `source`
+
+ See https://crowdin.com/page/cli-tool#configuration-file for more details
+ EOS
+ end
+
+ unless file['translation']
+ exit_now! <<-EOS.strip_heredoc
+ Files section misses required parameter `translation`
+
+ See https://crowdin.com/page/cli-tool#configuration-file for more details
+ EOS
+ end
+
file['source'] = '/' + file['source'] unless file['source'].start_with?('/')
#file['translation'] = '/' + file['translation'] unless file['translation'].start_with?('/')
if file['source'].include?('**')
if file['source'].scan('**').size > 1
- exit_now! <<EOS
-Source pattern `#{file['source']}` is not valid. The mask `**` can be used only once in the source pattern.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ Source pattern `#{file['source']}` is not valid. The mask `**` can be used only once in the source pattern.
+ EOS
elsif file['source'].scan('**').size == 1 and !file['source'].match(/\/\*\*\//)
- exit_now! <<EOS
-Source pattern `#{file['source']}` is not valid. The mask `**` must be surrounded by backslashes `/` in the source pattern.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ Source pattern `#{file['source']}` is not valid. The mask `**` must be surrounded by slashes `/` in the source pattern.
+ EOS
end
else
if file['translation'].include?('**')
- exit_now! <<EOS
-Translation pattern `#{file['translation']}` is not valid. The mask `**` can't be used.
-When using `**` in 'translation' pattern it will always contain sub-path from 'source' for certain file.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ Translation pattern `#{file['translation']}` is not valid. The mask `**` can't be used.
+ When using `**` in 'translation' pattern it will always contain sub-path from 'source' for certain file.
+ EOS
end
end
end #@config['files']
if @config['base_path']
@base_path = @config['base_path']
else
@base_path = Dir.pwd
- puts <<EOS
-Warning: Configuration file misses parameter `base_path` that defines your project root directory. Using `#{@base_path}` as a root directory.
-EOS
+ puts <<-EOS.strip_heredoc
+ Warning: Configuration file misses parameter `base_path` that defines your project root directory. Using `#{@base_path}` as a root directory.
+ EOS
end
@preserve_hierarchy = false
if @config['preserve_hierarchy']
@preserve_hierarchy = case @config['preserve_hierarchy']
when true
true
when false
false
else
- exit_now! <<EOS
-Parameter `preserve_hierarchy` allows values of true or false.
-EOS
+ exit_now! <<-EOS.strip_heredoc
+ Parameter `preserve_hierarchy` allows values of true or false.
+ EOS
end
end
@jipt_language = nil
if @config['jipt_language']
@@ -1051,10 +1098,10 @@
post do |globals, command, options, args|
# Post logic here
# Use skips_post before a command to skip this
# block on that command only
- #puts "Executed #{command.name}" if globals[:verbose]
+ # puts "Executed #{command.name}" if globals[:verbose]
end
on_error do |exception|
# Error logic here
# return false to skip default error handling