common.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/python
  2. # Code shared by translation conversion scripts.
  3. #
  4. # Copyright 2013 Google Inc.
  5. # https://developers.google.com/blockly/
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License");
  8. # you may not use this file except in compliance with the License.
  9. # You may obtain a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS,
  15. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. # See the License for the specific language governing permissions and
  17. # limitations under the License.
  18. import codecs
  19. import json
  20. import os
  21. from datetime import datetime
  22. class InputError(Exception):
  23. """Exception raised for errors in the input.
  24. Attributes:
  25. location -- where error occurred
  26. msg -- explanation of the error
  27. """
  28. def __init__(self, location, msg):
  29. Exception.__init__(self, '{0}: {1}'.format(location, msg))
  30. self.location = location
  31. self.msg = msg
  32. def read_json_file(filename):
  33. """Read a JSON file as UTF-8 into a dictionary, discarding @metadata.
  34. Args:
  35. filename: The filename, which must end ".json".
  36. Returns:
  37. The dictionary.
  38. Raises:
  39. InputError: The filename did not end with ".json" or an error occurred
  40. while opening or reading the file.
  41. """
  42. if not filename.endswith('.json'):
  43. raise InputError(filename, 'filenames must end with ".json"')
  44. try:
  45. # Read in file.
  46. with codecs.open(filename, 'r', 'utf-8') as infile:
  47. defs = json.load(infile)
  48. if '@metadata' in defs:
  49. del defs['@metadata']
  50. return defs
  51. except ValueError, e:
  52. print('Error reading ' + filename)
  53. raise InputError(filename, str(e))
  54. def _create_qqq_file(output_dir):
  55. """Creates a qqq.json file with message documentation for translatewiki.net.
  56. The file consists of key-value pairs, where the keys are message ids and
  57. the values are descriptions for the translators of the messages.
  58. What documentation exists for the format can be found at:
  59. http://translatewiki.net/wiki/Translating:Localisation_for_developers#Message_documentation
  60. The file should be closed by _close_qqq_file().
  61. Parameters:
  62. output_dir: The output directory.
  63. Returns:
  64. A pointer to a file to which a left brace and newline have been written.
  65. Raises:
  66. IOError: An error occurred while opening or writing the file.
  67. """
  68. qqq_file_name = os.path.join(os.curdir, output_dir, 'qqq.json')
  69. qqq_file = codecs.open(qqq_file_name, 'w', 'utf-8')
  70. print 'Created file: ' + qqq_file_name
  71. qqq_file.write('{\n')
  72. return qqq_file
  73. def _close_qqq_file(qqq_file):
  74. """Closes a qqq.json file created and opened by _create_qqq_file().
  75. This writes the final newlines and right brace.
  76. Args:
  77. qqq_file: A file created by _create_qqq_file().
  78. Raises:
  79. IOError: An error occurred while writing to or closing the file.
  80. """
  81. qqq_file.write('\n}\n')
  82. qqq_file.close()
  83. def _create_lang_file(author, lang, output_dir):
  84. """Creates a <lang>.json file for translatewiki.net.
  85. The file consists of metadata, followed by key-value pairs, where the keys
  86. are message ids and the values are the messages in the language specified
  87. by the corresponding command-line argument. The file should be closed by
  88. _close_lang_file().
  89. Args:
  90. author: Name and email address of contact for translators.
  91. lang: ISO 639-1 source language code.
  92. output_dir: Relative directory for output files.
  93. Returns:
  94. A pointer to a file to which the metadata has been written.
  95. Raises:
  96. IOError: An error occurred while opening or writing the file.
  97. """
  98. lang_file_name = os.path.join(os.curdir, output_dir, lang + '.json')
  99. lang_file = codecs.open(lang_file_name, 'w', 'utf-8')
  100. print 'Created file: ' + lang_file_name
  101. # string.format doesn't like printing braces, so break up our writes.
  102. lang_file.write('{\n\t"@metadata": {')
  103. lang_file.write("""
  104. \t\t"author": "{0}",
  105. \t\t"lastupdated": "{1}",
  106. \t\t"locale": "{2}",
  107. \t\t"messagedocumentation" : "qqq"
  108. """.format(author, str(datetime.now()), lang))
  109. lang_file.write('\t},\n')
  110. return lang_file
  111. def _close_lang_file(lang_file):
  112. """Closes a <lang>.json file created with _create_lang_file().
  113. This also writes the terminating left brace and newline.
  114. Args:
  115. lang_file: A file opened with _create_lang_file().
  116. Raises:
  117. IOError: An error occurred while writing to or closing the file.
  118. """
  119. lang_file.write('\n}\n')
  120. lang_file.close()
  121. def _create_key_file(output_dir):
  122. """Creates a keys.json file mapping Closure keys to Blockly keys.
  123. Args:
  124. output_dir: Relative directory for output files.
  125. Raises:
  126. IOError: An error occurred while creating the file.
  127. """
  128. key_file_name = os.path.join(os.curdir, output_dir, 'keys.json')
  129. key_file = open(key_file_name, 'w')
  130. key_file.write('{\n')
  131. print 'Created file: ' + key_file_name
  132. return key_file
  133. def _close_key_file(key_file):
  134. """Closes a key file created and opened with _create_key_file().
  135. Args:
  136. key_file: A file created by _create_key_file().
  137. Raises:
  138. IOError: An error occurred while writing to or closing the file.
  139. """
  140. key_file.write('\n}\n')
  141. key_file.close()
  142. def write_files(author, lang, output_dir, units, write_key_file):
  143. """Writes the output files for the given units.
  144. There are three possible output files:
  145. * lang_file: JSON file mapping meanings (e.g., Maze.turnLeft) to the
  146. English text. The base name of the language file is specified by the
  147. "lang" command-line argument.
  148. * key_file: JSON file mapping meanings to Soy-generated keys (long hash
  149. codes). This is only output if the parameter write_key_file is True.
  150. * qqq_file: JSON file mapping meanings to descriptions.
  151. Args:
  152. author: Name and email address of contact for translators.
  153. lang: ISO 639-1 source language code.
  154. output_dir: Relative directory for output files.
  155. units: A list of dictionaries with entries for 'meaning', 'source',
  156. 'description', and 'keys' (the last only if write_key_file is true),
  157. in the order desired in the output files.
  158. write_key_file: Whether to output a keys.json file.
  159. Raises:
  160. IOError: An error occurs opening, writing to, or closing a file.
  161. KeyError: An expected key is missing from units.
  162. """
  163. lang_file = _create_lang_file(author, lang, output_dir)
  164. qqq_file = _create_qqq_file(output_dir)
  165. if write_key_file:
  166. key_file = _create_key_file(output_dir)
  167. first_entry = True
  168. for unit in units:
  169. if not first_entry:
  170. lang_file.write(',\n')
  171. if write_key_file:
  172. key_file.write(',\n')
  173. qqq_file.write(',\n')
  174. lang_file.write(u'\t"{0}": "{1}"'.format(
  175. unit['meaning'],
  176. unit['source'].replace('"', "'")))
  177. if write_key_file:
  178. key_file.write('"{0}": "{1}"'.format(unit['meaning'], unit['key']))
  179. qqq_file.write(u'\t"{0}": "{1}"'.format(
  180. unit['meaning'],
  181. unit['description'].replace('"', "'").replace(
  182. '{lb}', '{').replace('{rb}', '}')))
  183. first_entry = False
  184. _close_lang_file(lang_file)
  185. if write_key_file:
  186. _close_key_file(key_file)
  187. _close_qqq_file(qqq_file)