Created
May 23, 2023 01:30
-
-
Save YusukeHosonuma/0362b8787062cb72afb8e66ed7f208cb to your computer and use it in GitHub Desktop.
Goodbye Localizable.strings with SwiftGen - SwiftGen によるローカライズを剥がすスクリプト
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
# ----------------------------------------------------------------------------- | |
# SwiftGen によるローカライズを剥がすスクリプト。 | |
# ----------------------------------------------------------------------------- | |
class String | |
# シンボル変換 | |
def symbol | |
# e.g. | |
# - OK → Ok | |
# - FAQ → Faq | |
if /^[A-Z]{2,}+$/ =~ self | |
self[0].upcase + self.downcase[1..] | |
else | |
self | |
end | |
end | |
# 先頭を小文字に | |
def property | |
self[0].downcase + self[1..self.length] | |
end | |
end | |
# | |
# Localizable.strings をパース。 | |
# | |
def parse_localizable(path) | |
File.open(path, "r") do |f| | |
f.each_line do |line| | |
if /"(.+?)"\s+=\s+"(.+?)"/ =~ line | |
key = $1 | |
val = $2 | |
# 間違って `..` が含まれるキー定義があるので複数を考慮 | |
ws = key.split(/\.+/) | |
ws = ws.map{ |w| w.symbol } | |
ws[-1] = ws[-1].property # 末尾の先頭は小文字に(e.g. `Text` → `text`) | |
key = 'L10n.' + ws.join('.') | |
$strings[key] = val | |
end | |
end | |
end | |
end | |
# | |
# Localizable.strings の値を取得する。 | |
# | |
def lookup(key) | |
value = "#{$strings[key]}" | |
raise "Key '#{key}' is not found." if value.empty? | |
value | |
end | |
KEY = "L10n\.[0-9a-zA-Z.]+" | |
TOKENS = "0-9a-zA-Z.\/*\s:\"\$:" | |
EXPR = "[#{TOKENS}(]+[#{TOKENS})]+?" | |
# | |
# 複数の引数を持つ定義を置換。 | |
# | |
def replace_multi_args(path) | |
text = File.open(path, "r") { |f| f.read } | |
pattern = /(#{KEY})\(\s*(#{EXPR})((,\s*#{EXPR})+)\s*\)/ | |
if pattern =~ text | |
value = lookup($1) | |
params = [$2] + $3.split(/\s*,\s*/)[1..] | |
params.each do |param| | |
value.sub!('%@', "\\(#{param})") | |
end | |
text.sub!(pattern, %|"#{value}"|) | |
end | |
File.open(path, "w") { |f| f.write(text) } | |
replace_multi_args(path) if pattern =~ text | |
end | |
# | |
# 単数の引数、または引数なしの定義を置換。 | |
# | |
def replace(path) | |
new_lines = [] | |
patterns = [ | |
/"\\\((#{KEY})\((#{EXPR})\)\)"/, | |
/(#{KEY})\(String\((#{EXPR})\)\)/, | |
/(#{KEY})\((#{EXPR})\)/, | |
/(#{KEY})\("\\\((#{EXPR})\)"\)/, | |
/(#{KEY})/, | |
] | |
File.open(path, "r") do |f| | |
f.each_line do |line| | |
if /\((#{KEY})\((#{EXPR})\)\)/ =~ line | |
match = Regexp.last_match[0] | |
value = lookup($1) | |
value.sub!('%@', "\\(#{$2})") | |
line.sub!(match, %|("#{value}")|) | |
elsif patterns.any? { |p| p =~ line } | |
match = Regexp.last_match[0] | |
value = lookup($1) | |
value.sub!('%@', "\\(#{$2})") if $2 | |
line.sub!(match, %|"#{value}"|) | |
end | |
new_lines << line | |
end | |
end | |
text = new_lines.join | |
File.open(path, "w") { |f| f.write(text) } | |
replace(path) if /#{KEY}/ =~ text | |
end | |
# | |
# ローカライズ関連のファイルを削除。 | |
# | |
def delete_files | |
['**/Localizable.strings', '**/Strings.swift'].each do |pattern| | |
Dir.glob(pattern) do |path| | |
File.delete(path) | |
end | |
end | |
end | |
# ----------------------------------------------------------------------------- | |
# main | |
# ----------------------------------------------------------------------------- | |
$strings = {} | |
# | |
# 1. Localizable.strings のパース | |
# | |
Dir.glob('**/Base.lproj/Localizable.strings') do |path| | |
parse_localizable(path) | |
end | |
# | |
# 2. 置換 | |
# | |
Dir.glob('**/*.swift') do |file| | |
next if file.end_with?('Strings.swift') | |
replace_multi_args(file) | |
replace(file) | |
end | |
# | |
# 3. 不要になるファイルの削除 | |
# | |
delete_files |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment