Numworks Epsilon  1.4.1
Graphing Calculator Operating System
i18n.py
Go to the documentation of this file.
1 #coding=utf-8
2 
3 import sys
4 import re
5 import unicodedata
6 import argparse
7 import io
8 
9 ion_special_characters = {
10  u'Σ': "Ion::Charset::CapitalSigma",
11  u'λ': "Ion::Charset::SmallLambda",
12  u'μ': "Ion::Charset::SmallMu",
13  u'σ': "Ion::Charset::SmallSigma",
14  u'≤': "Ion::Charset::LessEqual",
15  u'≈': "Ion::Charset::AlmostEqual"
16 }
17 
18 def ion_char(i18n_letter):
19  if i18n_letter == '\'':
20  return "'\\\''"
21  if ord(i18n_letter) < 128:
22  return "'" + i18n_letter + "'"
23  if i18n_letter in ion_special_characters:
24  return ion_special_characters[i18n_letter]
25  normalized = unicodedata.normalize("NFD", i18n_letter).encode('ascii', 'ignore')
26  #sys.stderr.write("Warning: Normalizing unicode character \"" + i18n_letter + "\" -> \"" + normalized + "\"\n")
27  return "'" + normalized.decode() + "'"
28 
29 def source_definition(i18n_string):
30  ion_characters = []
31  i = 0
32  while i < len(i18n_string):
33  if i18n_string[i] == '\\':
34  i = i+1
35  newChar = "'\\"+i18n_string[i]+"'"
36  ion_characters.append(newChar)
37  else:
38  ion_characters.append(ion_char(i18n_string[i]))
39  i = i+1
40  ion_characters.append("0")
41  return "{" + ", ".join(ion_characters) + "}"
42 
43 def split_line(line):
44  match = re.match(r"^(\w+)\s*=\s*\"(.*)\"$", line)
45  if not match:
46  sys.stderr.write("Error: Invalid line \"" + line + "\"\n")
47  sys.exit(-1)
48  return (match.group(1), source_definition(match.group(2)))
49 
50 def locale_from_filename(filename):
51  return re.match(r".*\.([a-z]+)\.i18n", filename).group(1)
52 
53 def parse_files(files):
54  data = {}
55  messages = set()
56  universal_messages = set()
57  for path in files:
58  locale = locale_from_filename(path)
59  if locale not in data:
60  data[locale] = {}
61  with io.open(path, "r", encoding='utf-8') as file:
62  for line in file:
63  name,definition = split_line(line)
64  if locale == "universal":
65  if name in messages:
66  sys.stderr.write("Error: Redefinition of message \"" + name + "\" as universal\n")
67  sys.exit(-1)
68  if name in universal_messages:
69  sys.stderr.write("Error: Redefinition of universal message \"" + name + "\"\n")
70  sys.exit(-1)
71  universal_messages.add(name)
72  else:
73  messages.add(name)
74  data[locale][name] = definition
75  return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data}
76 
77 def print_header(data, path, locales):
78  f = open(path, 'w')
79  f.write("#ifndef APPS_I18N_H\n")
80  f.write("#define APPS_I18N_H\n")
81  f.write("\n")
82  f.write("// This file is auto-generated by i18n.py\n")
83  f.write("\n")
84  f.write("#include <escher.h>\n")
85  f.write("\n")
86  f.write("namespace I18n {\n")
87  f.write("\n")
88  f.write("constexpr static int NumberOfLanguages = %d;\n" % len(locales))
89  f.write("\n")
90  f.write("enum class Message : uint16_t {\n")
91  for message in data["messages"]:
92  f.write(" " + message + ",\n")
93  f.write("\n")
94  f.write(" UniversalMessageMarker,\n")
95  f.write("\n")
96  for message in data["universal_messages"]:
97  f.write(" " + message + ",\n")
98  f.write("};\n")
99  f.write("\n")
100  f.write("enum class Language : uint16_t {\n")
101  f.write(" Default = 0,\n")
102  for locale in locales:
103  f.write(" " + locale.upper() + ",\n")
104  f.write("};\n")
105  f.write("\n")
106  f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n");
107  for locale in locales:
108  f.write(" Message::Language" + locale.upper() + ",\n")
109  f.write("};\n")
110  f.write("\n")
111  f.write("}\n")
112  f.write("\n")
113  f.write("#endif\n")
114  f.close()
115 
116 def print_implementation(data, path, locales):
117  f = open(path, 'w')
118  f.write("#include \"i18n.h\"\n")
119  f.write("#include \"global_preferences.h\"\n")
120  f.write("#include <assert.h>\n");
121  f.write("\n")
122  f.write("namespace I18n {\n")
123  f.write("\n")
124  for message in data["messages"]:
125  for locale in locales:
126  if not locale in data["data"]:
127  sys.stderr.write("Error: Undefined locale \"" + locale + "\"\n")
128  sys.exit(-1)
129  if not message in data["data"][locale]:
130  sys.stderr.write("Error: Undefined key \"" + message + "\" for locale \"" + locale + "\"\n")
131  sys.exit(-1)
132  f.write("constexpr static char " + locale + message + "[] = " + data["data"][locale][message] + ";\n")
133  f.write("\n")
134  f.write("constexpr static const char * messages[%d][%d] = {\n" % (len(data["messages"]), len(locales)))
135  for message in data["messages"]:
136  f.write(" {")
137  for locale in locales:
138  f.write(locale + message + ", ")
139  f.write("},\n")
140  f.write("};\n")
141  f.write("\n")
142  for message in data["universal_messages"]:
143  f.write("constexpr static char universal" + message + "[] = " + data["data"]["universal"][message] + ";\n")
144  f.write("\n")
145  f.write("constexpr static const char * universalMessages[%d] = {\n" % len(data["universal_messages"]))
146  for message in data["universal_messages"]:
147  f.write(" universal" + message + ",\n")
148  f.write("};\n")
149  f.write("\n")
150  f.write("const char * translate(Message m, Language l) {\n")
151  f.write(" assert(m != Message::UniversalMessageMarker);\n")
152  f.write(" int universalMessageOffset = (int)Message::UniversalMessageMarker+1;\n")
153  f.write(" if ((int)m >= universalMessageOffset) {\n")
154  f.write(" assert(universalMessages[(int)m - universalMessageOffset] != nullptr);\n")
155  f.write(" return universalMessages[(int)m - universalMessageOffset];\n")
156  f.write(" }\n")
157  f.write(" int languageIndex = (int)l;\n")
158  f.write(" if (l == Language::Default) {\n")
159  f.write(" languageIndex = (int) GlobalPreferences::sharedGlobalPreferences()->language();\n")
160  f.write(" }\n")
161  f.write(" assert(languageIndex > 0);\n")
162  f.write(" assert(((int)m*NumberOfLanguages+languageIndex-1)*sizeof(char *) < sizeof(messages));\n")
163  f.write(" return messages[(int)m][languageIndex-1];\n")
164  f.write("}\n")
165  f.write("\n")
166  f.write("}\n")
167  f.close()
168 
169 parser = argparse.ArgumentParser(description="Process some i18n files.")
170 parser.add_argument('--header', help='the .h file to generate')
171 parser.add_argument('--implementation', help='the .cpp file to generate')
172 parser.add_argument('--locales', nargs='+', help='locale to actually generate')
173 parser.add_argument('--files', nargs='+', help='an i18n file')
174 
175 args = parser.parse_args()
176 data = parse_files(args.files)
177 if args.header:
178  print_header(data, args.header, args.locales)
179 if args.implementation:
180  print_implementation(data, args.implementation, args.locales)
def print_header(data, path, locales)
Definition: i18n.py:77
def source_definition(i18n_string)
Definition: i18n.py:29
def print_implementation(data, path, locales)
Definition: i18n.py:116
def split_line(line)
Definition: i18n.py:43
def parse_files(files)
Definition: i18n.py:53
def locale_from_filename(filename)
Definition: i18n.py:50
def ion_char(i18n_letter)
Definition: i18n.py:18