Skip to content

Instantly share code, notes, and snippets.

@DmytroLisitsyn
Last active February 13, 2024 11:57
Show Gist options
  • Save DmytroLisitsyn/088a3a0cae7c179d33b2633726e052da to your computer and use it in GitHub Desktop.
Save DmytroLisitsyn/088a3a0cae7c179d33b2633726e052da to your computer and use it in GitHub Desktop.
Kassandra - localization constants generator
#!/usr/bin/python
import sys
import re
import os.path
def main():
strings_file = None
dict_file = None
output_file = None
for arg in sys.argv:
match = re.match(".+[.]strings$", arg)
if match:
strings_file = match.group(0)
continue
match = re.match(".+[.]stringsdict$", arg)
if match:
dict_file = match.group(0)
continue
match = re.match(".+[.]swift$", arg)
if match:
output_file = match.group(0)
continue
keys = []
keys += _parse_strings_file(strings_file)
keys += _parse_dict_file(dict_file)
keys.sort()
entries_string = _make_entries_string(keys, 1)
output_string = _make_output_string(entries_string)
_write_to_file(output_string, output_file)
def _write_to_file(string, file):
if file == None:
return
old_content = ""
if os.path.isfile(file):
content = open(file, "r")
if content:
old_content = content.read()
if old_content == string:
return
content = open(file, "w+")
if content:
if content.read() != string:
content.write(string)
def _parse_strings_file(file):
"""
Makes array of keys from Localizable.strings file.
Arguments:
file (str): the path to file
Returns:
list: array of keys retieved from file
"""
if file == None:
return []
keys = []
content = open(file, "r")
for line in content:
match = re.match("^[ ]*\".+\"[ ]?=", line)
if match:
key = re.sub("[$'\n'$'\t'\"= ]", "", match.group(0))
keys.append(key)
return keys
def _parse_dict_file(file):
if file == None:
return []
keys = []
content = open(file, "r").read()
match = re.findall("<key>[\\s\\S]*?<[/]dict>[$'\n'$'\t' ]*<[/]dict>", content)
if match == None:
return keys
for raw_key in match:
key = re.sub("<key>|<[/]key>[\\s\\S]*", "", raw_key)
keys.append(key)
return keys
def _make_output_string(entries):
boilerplate_prefix = """//
// This file is generated by Kassandra - localization constants generator.
//
import Foundation
enum Localizable {
"""
boilerplate_suffix = """}
private extension String {
func localized(arguments: CVarArg..., comment: String = \"\") -> String {
if arguments.isEmpty {
return NSLocalizedString(self, tableName: nil, bundle: Bundle(for: Localizable_BundleRef.self), value: self, comment: comment)
} else {
return withVaList(arguments) { arguments -> String in
let format = self.localized(comment: comment)
return NSString(format: format, arguments: arguments) as String
}
}
}
}
private final class Localizable_BundleRef {
}
"""
return "{}{}{}".format(boilerplate_prefix, entries, boilerplate_suffix)
def _make_entries_string(keys, depth):
entries_string = ""
hierarchy = []
for key in keys:
key_components = key.split(".")
new_hierarchy_depth = len(key_components) - 1
new_hierarchy = key_components[:new_hierarchy_depth]
common_hierarchy_depth = 0
for iteration in range(min(len(new_hierarchy), len(hierarchy))):
if new_hierarchy[iteration] == hierarchy[iteration]:
common_hierarchy_depth += 1
else:
break
entries_string += _make_closing_brackets(hierarchy, depth, common_hierarchy_depth)
entries_string += _make_opening_brackets(new_hierarchy, depth, common_hierarchy_depth)
signature = key_components[new_hierarchy_depth]
entries_string += _make_entry(signature, key, depth + new_hierarchy_depth)
hierarchy = new_hierarchy
entries_string += _make_closing_brackets(hierarchy, depth, 0)
return entries_string
def _make_indentation(depth):
"""
Makes indentation string giving its depth.
Arguments:
depth (int): level of indentation
Returns:
str: indentation string
"""
spacing = ""
for _ in range(depth):
spacing += " "
return spacing
def _make_opening_brackets(hierarchy, general_depth, common_hierarchy_depth):
brackets = ""
for iteration in range(len(hierarchy) - common_hierarchy_depth):
index = common_hierarchy_depth + iteration
indentation = _make_indentation(index + general_depth)
brackets += "{}enum {} {{\n".format(indentation, hierarchy[index])
return brackets
def _make_closing_brackets(hierarchy, general_depth, common_hierarchy_depth):
brackets = ""
for iteration in range(len(hierarchy) - common_hierarchy_depth):
index = len(hierarchy) - iteration - 1
indentation = _make_indentation(index + general_depth)
brackets += "{}}}\n".format(indentation)
return brackets
def _make_entry(signature, key, depth):
name = re.sub("%[@d]", "", signature)
name = _lowercase_first_letter(name)
name = _handle_keyword(name)
indentation = _make_indentation(depth)
match = re.findall("[A-Z]*[a-z0-9]*%[@d]", signature)
if match:
external_signature = ""
internal_signature = ""
for index in range(len(match)):
parameter_name = re.sub("%[@d]", "", _lowercase_first_letter(match[index]))
parameter_type = _make_parameter_type(match[index])
external_signature += "{}: {}".format(parameter_name, parameter_type)
internal_signature += _handle_keyword(parameter_name)
if index < len(match) - 1:
external_signature += ", "
internal_signature += ", "
entry = "{}static func {}({}) -> String {{\n".format(indentation, name, external_signature)
entry += "{}return \"{}\".localized(arguments: {})\n{}}}\n".format(_make_indentation(depth + 1), key, internal_signature, indentation)
return entry
else:
entry = "{}static var {}: String {{ return \"{}\".localized() }}\n".format(indentation, name, key)
return entry
def _lowercase_first_letter(string):
if len(string) == 0:
return string
else:
return string[0].lower() + string[1:]
def _handle_keyword(string):
keywords = ["continue", "switch", "default", "static", "final", "class","struct", "import", "extension", "return", "try", "let", "break", "case", "super", "private", "public", "internal", "guard", "self", "while", "do", "catch", "as", "true", "false", "override", "lazy", "get", "set"]
for keyowrd in keywords:
if string == keyowrd:
return "`" + string + "`"
return string
def _make_parameter_type(string):
if "%@" in string:
return "String"
elif "%d" in string:
return "Int"
elif "%f" in string:
return "Double"
else:
return "Undefined"
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment