Last active
July 20, 2017 17:32
-
-
Save huguesbr/375730d567020da83836483074a67ff9 to your computer and use it in GitHub Desktop.
Automatically replace annotated String (see format below) in Swift with corresponding tr(...) calls for https://github.com/AliSoftware/SwiftGen. Copy both into the project and call 'sh replaceAnnotatedLocalizeString.sh'
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# take annotated string and transforms it to the tr(...) format as well as creating the localizable.strings entry | |
# arguments | |
localizable=ARGV[0] | |
filename=ARGV[1] | |
def makeFirstLetterUpper(string) | |
string[0].upcase + string[1..-1] | |
end | |
def namify(string) | |
# take a string | |
# return: transform it to a localizing key | |
# 1Hello,world -> _1HelloWorld | |
# treat all whitespace, "" and , like a word split and uppercase next letwordter | |
string = string.split(/\s+/).map { |a| makeFirstLetterUpper(a) }.join("") | |
string = string.split(",").map { |a| makeFirstLetterUpper(a) }.join("") | |
string = string.split("\"").map { |a| makeFirstLetterUpper(a) }.join("") | |
# handle _ like . (except first one) | |
string = string.gsub("(.)\_", '\1.') | |
# handle any others characters like a word split, strip it and uppercase next word | |
string = string.split(/[^a-zA-Z0-9._]/).map { |a| makeFirstLetterUpper(a) if a.length > 0 }.join("") | |
# prefix first digit with _ | |
string = string.gsub(/^([0-9])/, '_\1') | |
end | |
def variabilize(string, types) | |
# take a string and some eventual types or variables | |
# return: the string with the named argument replaced by | |
# "Hello world" -> "Hello world", [] | |
# "Hello \(world)" -> "Hello %@", [world] | |
variables = string.scan(/\\\(([^)]+)\)/) | |
if variables.length > 0 | |
# string contain variable | |
variables = variables.map { |k| k[0] } | |
for t in types | |
string = string.sub(/\\\([^)]+\)/, t) | |
end | |
end | |
[string, variables] | |
end | |
# check string | |
match = localizable.scan(/"(.*)" \/\/ !tr(: (.*))?/i) | |
# "String" // tr: Key | |
# "String" // tr | |
if match.length > 0 | |
s = match[0][0] | |
# determine key | |
t = [] | |
custom_key = false | |
if match[0][2] == nil | |
# "Hello Wolrd" // tr | |
# k = HelloWorld | |
k = tr = namify(s) | |
else | |
# "Hello \(count) \(world)" // tr: HelloWorld(%d, %@) | |
# k = HelloWorld | |
# v = %d, %@ | |
custom_key = true | |
kv = match[0][2] | |
k = namify(kv.split("(").first) | |
t = kv.split("(").last.split(")").first.split(", ") | |
end | |
# transform into localize and extract variables | |
# "Hello \(count) \(world)" -> "Hello %d %@", [count, world] | |
s, v = variabilize(s, t) | |
if t.count == 0 && v.count > 0 | |
puts "!!! ERROR: missing arguments types" | |
exit(0) | |
end | |
# build and write localizable.strings entry | |
# "HelloWorld" = "Hello %d %@"; | |
l = "\"#{k}\" = \"#{s}\";" | |
File.open(filename, 'a') { |file| file.write(l + "\n") } | |
# add arguments to enum call | |
# tr(.HelloWorld(count, world)) | |
if v.length > 0 | |
vs = v.join(", ") | |
tr = "#{k}(#{vs})" | |
else | |
tr = k | |
end | |
else | |
puts "!!! ERROR: invalid syntax" | |
exit(0) | |
end | |
# re-adding annotated syntax at the end of tr syntax | |
# let a = tr(.Hello(world)) // translated from "Hello, \(world)", Hello(%@) | |
comment = localizable.split("// !tr").first.strip | |
comment += ", #{k}" if custom_key | |
comment += "(#{t.join(", ")})" if v.count > 0 | |
# return tr syntax | |
puts "tr(.#{tr}) // translated from #{comment}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Run this inside the project to replace annotated string calls with swiftgen calls in all .swift files. | |
# Do not forget to make a backup before. | |
# Usage: ./replaceAnnotatedLocalizeString.sh Localizable.strings | |
# | |
# Annotation look like this: | |
# | |
# Option 1 | |
# let s = "Next Step" // !tr | |
# will be converted to: | |
# let s = tr(.NextStep) // translated from "Next Step" | |
# will add following string to localized.strings | |
# "NextStep" = "Next Step"; | |
# | |
# Option 2 | |
# let s = "Next Step" // !tr: MyKey | |
# will be converted to: | |
# let s = tr(.MyKey) // translated from "Next Step" | |
# will add following string to localized.strings | |
# "MyKey" = "Next Step"; | |
# | |
# Option 3 (mandatory if arguments) | |
# let s = "Hello \(name), can you count to \(number)" // !tr: Greeting(%@, %d) | |
# will be converted to: | |
# let s = tr(.Greeting(name, number)) // translated from "Hello \(name), can you count to \(number)" | |
# will add following string to localized.strings | |
# "Greeting" = "Hello %@, can you count to %d"; | |
# | |
# Limitations: | |
# - many and a lot unknown, undocumented | |
# - complex inner string variable expression: "Hello \(this + (that - 3))" | |
# - multi line string | |
# - more than string line of code: | |
# - replace: function(string: "My String \(blah)", options: []) // tr: String(%@) | |
# - by: | |
# - let aString = "My String \(blah)" // tr | |
# - function(string: aString, options: []) | |
# | |
# Recommendations: | |
# - add all annotations to your code, then commit, then run the script and diff | |
# - try to simplify your string assignement | |
# | |
# Please read your commits before commiting and then complaining.. :P | |
# check arguments | |
if [ "$#" -ne 1 ]; then | |
echo "Usage $0 path_to_localized_strings" | |
exit 1 | |
fi | |
# get localizable.strings path | |
localizable_path=$1 | |
# find all swift files | |
find . -type f | grep ".swift" > swiftindex.temp | |
while IFS= read -r filename | |
do | |
# extract all annotated lines | |
grep -o "\"[^\"]*\" // !tr: .*$" "$filename" > strings.temp | |
grep -o "\"[^\"]*\" // !tr$" "$filename" >> strings.temp | |
# process each lines | |
while IFS= read -r localizable | |
do | |
# call replacement script | |
replacement=$(ruby scripts/processAnnotatedString.rb "$localizable" "$localizable_path") | |
echo "$replacement" | |
# escaping replacement | |
localizable_escaped=$(echo "$localizable" | sed -e 's/[]\/$*.^|[]/\\&/g') | |
replacement_escaped=$(echo "$replacement" | sed -e 's/[]\/$*.^|[]/\\&/g') | |
# replacing in files (we could use line number...) | |
sed -i .bak "s/$localizable_escaped/$replacement_escaped/g" $filename | |
rm "$filename.bak" | |
done < strings.temp | |
rm strings.temp | |
done < swiftindex.temp | |
rm swiftindex.temp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# testing processAnnotatedString | |
def expect(a, b, test) | |
if a == b | |
puts "OK: #{test} >>>> #{a}" | |
else | |
puts "KO: #{test}:\ngot:\t\t#{b}\nexpecting:\t#{a}" | |
end | |
end | |
def test(string, expectedTR, expectedLocalizable) | |
tmp = "localizable.tmp" | |
r = IO.popen(['ruby', "scripts/processAnnotatedString.rb", string, tmp]).read.strip | |
expect(expectedTR, r, string) | |
r = File.exist?(tmp) && File.read(tmp).strip() || "" | |
expect(expectedLocalizable, r, string) | |
File.exist?(tmp) && File.delete(tmp) | |
end | |
test('"Hello World" // !tr', 'tr(.HelloWorld) // translated from "Hello World"', '"HelloWorld" = "Hello World";') | |
test('"12 monkeys" // !tr', 'tr(._12Monkeys) // translated from "12 monkeys"', '"_12Monkeys" = "12 monkeys";') | |
test('"It\'s complex" // !tr', 'tr(.ItSComplex) // translated from "It\'s complex"', '"ItSComplex" = "It\'s complex";') | |
test('"Hello \(world)" // !tr: Hello(%@)', 'tr(.Hello(world)) // translated from "Hello \(world)", Hello(%@)', '"Hello" = "Hello %@";') | |
test('"Hello again" // !tr: Hello', 'tr(.Hello) // translated from "Hello again", Hello', '"Hello" = "Hello again";') | |
test('"Hello \(world) from \(count) humans" // !tr: Hello(%@, %d)', 'tr(.Hello(world, count)) // translated from "Hello \(world) from \(count) humans", Hello(%@, %d)', '"Hello" = "Hello %@ from %d humans";') | |
test('"Hello \(world) from \(count) humans" // !tr: Hello(%@, %d)', 'tr(.Hello(world, count)) // translated from "Hello \(world) from \(count) humans", Hello(%@, %d)', '"Hello" = "Hello %@ from %d humans";') | |
test('"Hello \(world) from \(count) humans" // !tr', '!!! ERROR: missing arguments types', '') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Automation of "annotated" swift localizable strings conversion, to be use with https://github.com/AliSoftware/SwiftGen
Heavily inspired of https://gist.github.com/Lutzifer/3e7d967f73e38b57d4355f23274f303d
Run this inside the project to replace annotated string (see exemple below) with swiftgen enum in all .swift files, and populate the
Localizable.strings
's file.!!! Do not forget to make a backup before !!!
Usage:
./replaceAnnotatedLocalizeString.sh Localizable.strings
Supported syntax:
let s = "Next Step" // !tr
let s = "Next Step" // !tr: MyKey
let s = "Hello \(name), can you count to \(number)" // !tr: Greeting(%@, %d)
See more information in
replaceAnnotatedLocalizeString.sh
's header