Skip to content

Instantly share code, notes, and snippets.

@liudasbar
Created October 23, 2023 10:46
Show Gist options
  • Save liudasbar/c2f93546b99e493577fb0754742786c9 to your computer and use it in GitHub Desktop.
Save liudasbar/c2f93546b99e493577fb0754742786c9 to your computer and use it in GitHub Desktop.
Localizable strings generator for Swift projects in Xcode
import os
def parse_input(input_path):
with open(input_path, 'r') as f:
lines = f.readlines()
translations = {}
for line in lines:
line = line.strip()
if not line.startswith('"') or '=' not in line:
continue
key, value = line.split('=', 1)
key = key.strip('" ')
value = value.strip(' ";')
translations[key] = value
return translations
def build_translation_tree(translations):
tree = {}
for key, value in translations.items():
parts = key.split('.')
node = tree
for part in parts:
if part not in node:
node[part] = {}
node = node[part]
node['value'] = key
return tree
def to_struct_name(name):
parts = name.split('.')
if len(parts) > 1:
return parts[-2]
return parts[0]
def generate_swift_code(t, translations, depth=1):
code = ""
indentation = ' ' * depth
for key, subtree in t.items():
if key == 'value':
continue
if 'value' in subtree:
# Check if the translation value has a placeholder
has_placeholder = "%@" in translations[subtree["value"]]
if has_placeholder:
code += f'{indentation}public static func {key}(_ value: String) -> String {{\n'
code += f'{indentation} return String(format: NSLocalizedString("{subtree["value"]}", comment: ""), value)\n'
code += f'{indentation}}}\n'
else:
code += f'{indentation}public static var {key}: String {{\n'
code += f'{indentation} return NSLocalizedString("{subtree["value"]}", comment: "")\n'
code += f'{indentation}}}\n'
else:
struct_name = to_struct_name(key)
code += f'{indentation}public struct {struct_name} {{\n'
code += generate_swift_code(subtree, translations, depth + 1)
code += f'{indentation}}}\n'
return code
def main():
input_file_path = './Path/To/Localizable.strings/File'
output_file_path = './Path/To/Strings.swift/File'
translations = parse_input(input_file_path)
translations_tree = build_translation_tree(translations)
output = 'public struct Strings {\n'
output += ' // MARK: - Common\n'
for key, subtree in translations_tree.items():
struct_name = to_struct_name(key)
if 'value' in subtree:
output += f' public static var {key}: String {{\n'
output += f' NSLocalizedString("{subtree["value"]}", comment: "")\n'
output += ' }\n'
else:
output += f' public struct {struct_name} {{\n'
output += generate_swift_code(subtree, translations, 2)
output += ' }\n'
output += '}\n'
with open(output_file_path, 'w') as f:
f.write(output)
if __name__ == "__main__":
main()
@liudasbar
Copy link
Author

Setup instructions:

  1. Put this file in your project's root folder.
  2. In main target's Build Phases section, create new Run Script phase.
  3. Rename it to "Generate Strings" for better phases readability.
  4. Move it to the very top as it allows you (usually, it would not go above "Run Build Tool Plug-ins" or "Target Dependencies").
  5. Make sure Shell is "/bin/sh".
  6. Add this script: "/usr/bin/python3 "${SRCROOT}/generate_strings.py" (without quotes).
  7. Make sure "For install builds only" and "Based on dependency analysis" are deselected.
  8. Build the project with some written strings keys and values in your Localizable.strings file.
  9. The script should generate some output in your Strings.swift file.
  10. Use the struct in your code.

This generation supports nested strings. It also supports single formatted string value (can be declared as "%@" in Localizable.strings file, e.g. "User.header" = "User %@";. So you can use it like this: Strings.User.header(userName)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment