lionbridge_import.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import argparse #To get the source directory from command line arguments.
  4. import io # To fix encoding issues in Windows
  5. import os #To find files from the source.
  6. import os.path #To find files from the source and the destination path.
  7. cura_files = {"cura", "fdmprinter.def.json", "fdmextruder.def.json"}
  8. uranium_files = {"uranium"}
  9. ## Imports translation files from Lionbridge.
  10. #
  11. # Lionbridge has a bit of a weird export feature. It exports it to the same
  12. # file type as what we imported, so that's a .pot file. However this .pot file
  13. # only contains the translations, so the header is completely empty. We need
  14. # to merge those translations into our existing files so that the header is
  15. # preserved.
  16. def lionbridge_import(source: str) -> None:
  17. print("Importing from:", source)
  18. print("Importing to Cura:", destination_cura())
  19. print("Importing to Uranium:", destination_uranium())
  20. for language in (directory for directory in os.listdir(source) if os.path.isdir(os.path.join(source, directory))):
  21. print("================ Processing language:", language, "================")
  22. directory = os.path.join(source, language)
  23. for file_pot in (file for file in os.listdir(directory) if file.endswith(".pot")):
  24. source_file = file_pot[:-4] #Strip extension.
  25. if source_file in cura_files:
  26. destination_file = os.path.join(destination_cura(), language.replace("-", "_"), source_file + ".po")
  27. print("Merging", source_file, "(Cura) into", destination_file)
  28. elif source_file in uranium_files:
  29. destination_file = os.path.join(destination_uranium(), language.replace("-", "_"), source_file + ".po")
  30. print("Merging", source_file, "(Uranium) into", destination_file)
  31. else:
  32. raise Exception("Unknown file: " + source_file + "... Is this Cura or Uranium?")
  33. with io.open(os.path.join(directory, file_pot), encoding = "utf8") as f:
  34. source_str = f.read()
  35. with io.open(destination_file, encoding = "utf8") as f:
  36. destination_str = f.read()
  37. result = merge(source_str, destination_str)
  38. with io.open(destination_file, "w", encoding = "utf8") as f:
  39. f.write(result)
  40. ## Gets the destination path to copy the translations for Cura to.
  41. # \return Destination path for Cura.
  42. def destination_cura() -> str:
  43. return os.path.abspath(os.path.join(__file__, "..", "..", "resources", "i18n"))
  44. ## Gets the destination path to copy the translations for Uranium to.
  45. # \return Destination path for Uranium.
  46. def destination_uranium() -> str:
  47. try:
  48. import UM
  49. except ImportError:
  50. relative_path = os.path.join(__file__, "..", "..", "..", "Uranium", "resources", "i18n", "uranium.pot")
  51. absolute_path = os.path.abspath(relative_path)
  52. if os.path.exists(absolute_path):
  53. absolute_path = os.path.abspath(os.path.join(absolute_path, ".."))
  54. print("Uranium is at:", absolute_path)
  55. return absolute_path
  56. else:
  57. raise Exception("Can't find Uranium. Please put UM on the PYTHONPATH or put the Uranium folder next to the Cura folder. Looked for: " + absolute_path)
  58. return os.path.abspath(os.path.join(UM.__file__, "..", "..", "resources", "i18n"))
  59. ## Merges translations from the source file into the destination file if they
  60. # were missing in the destination file.
  61. # \param source The contents of the source .po file.
  62. # \param destination The contents of the destination .po file.
  63. def merge(source: str, destination: str) -> str:
  64. result_lines = []
  65. last_destination = {
  66. "msgctxt": "\"\"\n",
  67. "msgid": "\"\"\n",
  68. "msgstr": "\"\"\n",
  69. "msgid_plural": "\"\"\n"
  70. }
  71. current_state = "none"
  72. for line in destination.split("\n"):
  73. if line.startswith("msgctxt \""):
  74. current_state = "msgctxt"
  75. line = line[8:]
  76. last_destination[current_state] = ""
  77. elif line.startswith("msgid \""):
  78. current_state = "msgid"
  79. line = line[6:]
  80. last_destination[current_state] = ""
  81. elif line.startswith("msgstr \""):
  82. current_state = "msgstr"
  83. line = line[7:]
  84. last_destination[current_state] = ""
  85. elif line.startswith("msgid_plural \""):
  86. current_state = "msgid_plural"
  87. line = line[13:]
  88. last_destination[current_state] = ""
  89. if line.startswith("\"") and line.endswith("\""):
  90. last_destination[current_state] += line + "\n"
  91. else: #White lines or comment lines trigger us to search for the translation in the source file.
  92. if last_destination["msgstr"] == "\"\"\n" and last_destination["msgid"] != "\"\"\n": #No translation for this yet!
  93. last_destination["msgstr"] = find_translation(source, last_destination["msgctxt"], last_destination["msgid"]) #Actually place the translation in.
  94. if last_destination["msgctxt"] != "\"\"\n" or last_destination["msgid"] != "\"\"\n" or last_destination["msgid_plural"] != "\"\"\n" or last_destination["msgstr"] != "\"\"\n":
  95. if last_destination["msgctxt"] != "\"\"\n":
  96. result_lines.append("msgctxt {msgctxt}".format(msgctxt = last_destination["msgctxt"][:-1])) #The [:-1] to strip the last newline.
  97. result_lines.append("msgid {msgid}".format(msgid = last_destination["msgid"][:-1]))
  98. if last_destination["msgid_plural"] != "\"\"\n":
  99. result_lines.append("msgid_plural {msgid_plural}".format(msgid_plural = last_destination["msgid_plural"][:-1]))
  100. else:
  101. result_lines.append("msgstr {msgstr}".format(msgstr = last_destination["msgstr"][:-1]))
  102. last_destination = {
  103. "msgctxt": "\"\"\n",
  104. "msgid": "\"\"\n",
  105. "msgstr": "\"\"\n",
  106. "msgid_plural": "\"\"\n"
  107. }
  108. result_lines.append(line) #This line itself.
  109. return "\n".join(result_lines)
  110. ## Finds a translation in the source file.
  111. # \param source The contents of the source .po file.
  112. # \param msgctxt The ctxt of the translation to find.
  113. # \param msgid The id of the translation to find.
  114. def find_translation(source: str, msgctxt: str, msgid: str) -> str:
  115. last_source = {
  116. "msgctxt": "\"\"\n",
  117. "msgid": "\"\"\n",
  118. "msgstr": "\"\"\n",
  119. "msgid_plural": "\"\"\n"
  120. }
  121. current_state = "none"
  122. for line in source.split("\n"):
  123. if line.startswith("msgctxt \""):
  124. current_state = "msgctxt"
  125. line = line[8:]
  126. last_source[current_state] = ""
  127. elif line.startswith("msgid \""):
  128. current_state = "msgid"
  129. line = line[6:]
  130. last_source[current_state] = ""
  131. elif line.startswith("msgstr \""):
  132. current_state = "msgstr"
  133. line = line[7:]
  134. last_source[current_state] = ""
  135. elif line.startswith("msgid_plural \""):
  136. current_state = "msgid_plural"
  137. line = line[13:]
  138. last_source[current_state] = ""
  139. if line.startswith("\"") and line.endswith("\""):
  140. last_source[current_state] += line + "\n"
  141. else: #White lines trigger us to process this translation. Is it the correct one?
  142. #Process the source and destination keys for comparison independent of newline technique.
  143. source_ctxt = "".join((line.strip()[1:-1] for line in last_source["msgctxt"].split("\n")))
  144. source_id = "".join((line.strip()[1:-1] for line in last_source["msgid"].split("\n")))
  145. dest_ctxt = "".join((line.strip()[1:-1] for line in msgctxt.split("\n")))
  146. dest_id = "".join((line.strip()[1:-1] for line in msgid.split("\n")))
  147. if source_ctxt == dest_ctxt and source_id == dest_id:
  148. if last_source["msgstr"] == "\"\"\n" and last_source["msgid_plural"] == "\"\"\n":
  149. print("!!! Empty translation for {" + dest_ctxt + "}", dest_id, "!!!")
  150. return last_source["msgstr"]
  151. last_source = {
  152. "msgctxt": "\"\"\n",
  153. "msgid": "\"\"\n",
  154. "msgstr": "\"\"\n",
  155. "msgid_plural": "\"\"\n"
  156. }
  157. #Still here? Then the entire msgctxt+msgid combination was not found at all.
  158. print("!!! Missing translation for {" + msgctxt.strip() + "}", msgid.strip(), "!!!")
  159. return "\"\"\n"
  160. if __name__ == "__main__":
  161. argparser = argparse.ArgumentParser(description = "Import translation files from Lionbridge.")
  162. argparser.add_argument("source")
  163. args = argparser.parse_args()
  164. lionbridge_import(args.source)