Files
Upsilon/apps/i18n.py
2019-05-03 15:53:20 +02:00

187 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
# This script gathers all .i18n files and aggregates them as a pair of .h/.cpp
# file.
# In practice, it enforces a NFKD normalization. Because Epsilon does not
# properly draw upper case letters with accents, we remove them here.
# It works with Python 2 and Python 3
import sys
import re
import unicodedata
import argparse
import io
def source_definition(i18n_string):
s = unicodedata.normalize("NFKD", i18n_string)
result = u"\""
i = 0
length = len(s)
checkForCombining = False
while i < length:
copyCodePoint = True
if checkForCombining:
# We remove combining code points, which are between 0x300 and 0x36F
# (for the non-extended set)
copyCodePoint = (ord(s[i]) < 0x300) or (ord(s[i]) > 0x36F)
checkForCombining = False
if copyCodePoint:
# Remove the uppercase characters with combining chars
checkForCombining = s[i].isupper()
result = result + s[i]
i = i+1
result = result + u"\""
return result.encode("utf-8")
def split_line(line):
match = re.match(r"^(\w+)\s*=\s*\"(.*)\"$", line)
if not match:
sys.stderr.write("Error: Invalid line \"" + line + "\"\n")
sys.exit(-1)
return (match.group(1), source_definition(match.group(2)))
def locale_from_filename(filename):
return re.match(r".*\.([a-z]+)\.i18n", filename).group(1)
def parse_files(files):
data = {}
messages = set()
universal_messages = set()
for path in files:
locale = locale_from_filename(path)
if locale not in data:
data[locale] = {}
with io.open(path, "r", encoding='utf-8') as file:
for line in file:
name,definition = split_line(line)
if locale == "universal":
if name in messages:
sys.stderr.write("Error: Redefinition of message \"" + name + "\" as universal\n")
sys.exit(-1)
if name in universal_messages:
sys.stderr.write("Error: Redefinition of universal message \"" + name + "\"\n")
sys.exit(-1)
universal_messages.add(name)
else:
messages.add(name)
data[locale][name] = definition
return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data}
def print_header(data, path, locales):
f = open(path, "w")
f.write("#ifndef APPS_I18N_H\n")
f.write("#define APPS_I18N_H\n\n")
f.write("// This file is auto-generated by i18n.py\n\n")
f.write("#include <escher.h>\n\n")
f.write("namespace I18n {\n\n")
f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales))
# Messages enumeration
f.write("enum class Message : uint16_t {\n")
f.write(" Default = 0,\n")
for message in data["universal_messages"]:
f.write(" " + message + ",\n")
f.write("\n")
f.write(" LocalizedMessageMarker,\n\n")
for message in data["messages"]:
f.write(" " + message + ",\n")
f.write("};\n\n")
# Languages enumeration
f.write("enum class Language : uint16_t {\n")
f.write(" Default = 0,\n")
for locale in locales:
f.write(" " + locale.upper() + ",\n")
f.write("};\n\n")
# Language names
f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n");
for locale in locales:
f.write(" Message::Language" + locale.upper() + ",\n")
f.write("};\n\n")
f.write("}\n\n")
f.write("#endif\n")
f.close()
def print_implementation(data, path, locales):
f = open(path, "w")
f.write("#include \"i18n.h\"\n")
f.write("#include <apps/global_preferences.h>\n")
f.write("#include <assert.h>\n\n")
f.write("namespace I18n {\n\n")
# Write the default message
f.write("constexpr static char universalDefault[] = {0};\n")
# Write the universal messages
for message in data["universal_messages"]:
f.write("constexpr static char universal" + message + "[] = ")
f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes
f.write(data["data"]["universal"][message])
f = open(path, "a") # Re-open the file as text
f.write(";\n")
f.write("\n")
f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1))
f.write(" universalDefault,\n")
for message in data["universal_messages"]:
f.write(" universal" + message + ",\n")
f.write("};\n\n")
# Write the localized messages
for message in data["messages"]:
for locale in locales:
if not locale in data["data"]:
sys.stderr.write("Error: Undefined locale \"" + locale + "\"\n")
sys.exit(-1)
if not message in data["data"][locale]:
sys.stderr.write("Error: Undefined key \"" + message + "\" for locale \"" + locale + "\"\n")
sys.exit(-1)
f.write("constexpr static char " + locale + message + "[] = ")
f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes
f.write(data["data"][locale][message])
f = open(path, "a") # Re-open the file as text
f.write(";\n")
f.write("\n")
f.write("constexpr static const char * messages[%d][%d] = {\n" % (len(data["messages"]), len(locales)))
for message in data["messages"]:
f.write(" {")
for locale in locales:
f.write(locale + message + ", ")
f.write("},\n")
f.write("};\n\n")
# Write the translate method
f.write("const char * translate(Message m, Language l) {\n")
f.write(" assert(m != Message::LocalizedMessageMarker);\n")
f.write(" int localizedMessageOffset = (int)Message::LocalizedMessageMarker+1;\n")
f.write(" if ((int)m < localizedMessageOffset) {\n")
f.write(" assert(universalMessages[(int)m] != nullptr);\n")
f.write(" return universalMessages[(int)m];\n")
f.write(" }\n")
f.write(" int languageIndex = (int)l;\n")
f.write(" if (l == Language::Default) {\n")
f.write(" languageIndex = (int) GlobalPreferences::sharedGlobalPreferences()->language();\n")
f.write(" }\n")
f.write(" assert(languageIndex > 0);\n")
f.write(" int messageIndex = (int)m - localizedMessageOffset;\n")
f.write(" assert((messageIndex*NumberOfLanguages+languageIndex-1)*sizeof(char *) < sizeof(messages));\n")
f.write(" return messages[messageIndex][languageIndex-1];\n")
f.write("}\n\n")
f.write("}\n")
f.close()
parser = argparse.ArgumentParser(description="Process some i18n files.")
parser.add_argument('--header', help='the .h file to generate')
parser.add_argument('--implementation', help='the .cpp file to generate')
parser.add_argument('--locales', nargs='+', help='locale to actually generate')
parser.add_argument('--files', nargs='+', help='an i18n file')
args = parser.parse_args()
data = parse_files(args.files)
if args.header:
print_header(data, args.header, args.locales)
if args.implementation:
print_implementation(data, args.implementation, args.locales)