lionbridge_import.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. print(os.path.abspath(relative_path))
  52. if os.path.exists(relative_path):
  53. return os.path.abspath(relative_path)
  54. else:
  55. raise Exception("Can't find Uranium. Please put UM on the PYTHONPATH or put the Uranium folder next to the Cura folder.")
  56. return os.path.abspath(os.path.join(UM.__file__, "..", "..", "resources", "i18n"))
  57. ## Merges translations from the source file into the destination file if they
  58. # were missing in the destination file.
  59. # \param source The contents of the source .po file.
  60. # \param destination The contents of the destination .po file.
  61. def merge(source: str, destination: str) -> str:
  62. result_lines = []
  63. last_destination = {
  64. "msgctxt": "\"\"\n",
  65. "msgid": "\"\"\n",
  66. "msgstr": "\"\"\n",
  67. "msgid_plural": "\"\"\n"
  68. }
  69. current_state = "none"
  70. for line in destination.split("\n"):
  71. if line.startswith("msgctxt \""):
  72. current_state = "msgctxt"
  73. line = line[8:]
  74. last_destination[current_state] = ""
  75. elif line.startswith("msgid \""):
  76. current_state = "msgid"
  77. line = line[6:]
  78. last_destination[current_state] = ""
  79. elif line.startswith("msgstr \""):
  80. current_state = "msgstr"
  81. line = line[7:]
  82. last_destination[current_state] = ""
  83. elif line.startswith("msgid_plural \""):
  84. current_state = "msgid_plural"
  85. line = line[13:]
  86. last_destination[current_state] = ""
  87. if line.startswith("\"") and line.endswith("\""):
  88. last_destination[current_state] += line + "\n"
  89. else: #White lines or comment lines trigger us to search for the translation in the source file.
  90. if last_destination["msgstr"] == "\"\"\n" and last_destination["msgid"] != "\"\"\n": #No translation for this yet!
  91. last_destination["msgstr"] = find_translation(source, last_destination["msgctxt"], last_destination["msgid"]) #Actually place the translation in.
  92. if last_destination["msgctxt"] != "\"\"\n" or last_destination["msgid"] != "\"\"\n" or last_destination["msgid_plural"] != "\"\"\n" or last_destination["msgstr"] != "\"\"\n":
  93. if last_destination["msgctxt"] != "\"\"\n":
  94. result_lines.append("msgctxt {msgctxt}".format(msgctxt = last_destination["msgctxt"][:-1])) #The [:-1] to strip the last newline.
  95. result_lines.append("msgid {msgid}".format(msgid = last_destination["msgid"][:-1]))
  96. if last_destination["msgid_plural"] != "\"\"\n":
  97. result_lines.append("msgid_plural {msgid_plural}".format(msgid_plural = last_destination["msgid_plural"][:-1]))
  98. else:
  99. result_lines.append("msgstr {msgstr}".format(msgstr = last_destination["msgstr"][:-1]))
  100. last_destination = {
  101. "msgctxt": "\"\"\n",
  102. "msgid": "\"\"\n",
  103. "msgstr": "\"\"\n",
  104. "msgid_plural": "\"\"\n"
  105. }
  106. result_lines.append(line) #This line itself.
  107. return "\n".join(result_lines)
  108. ## Finds a translation in the source file.
  109. # \param source The contents of the source .po file.
  110. # \param msgctxt The ctxt of the translation to find.
  111. # \param msgid The id of the translation to find.
  112. def find_translation(source: str, msgctxt: str, msgid: str) -> str:
  113. last_source = {
  114. "msgctxt": "\"\"\n",
  115. "msgid": "\"\"\n",
  116. "msgstr": "\"\"\n",
  117. "msgid_plural": "\"\"\n"
  118. }
  119. current_state = "none"
  120. for line in source.split("\n"):
  121. if line.startswith("msgctxt \""):
  122. current_state = "msgctxt"
  123. line = line[8:]
  124. last_source[current_state] = ""
  125. elif line.startswith("msgid \""):
  126. current_state = "msgid"
  127. line = line[6:]
  128. last_source[current_state] = ""
  129. elif line.startswith("msgstr \""):
  130. current_state = "msgstr"
  131. line = line[7:]
  132. last_source[current_state] = ""
  133. elif line.startswith("msgid_plural \""):
  134. current_state = "msgid_plural"
  135. line = line[13:]
  136. last_source[current_state] = ""
  137. if line.startswith("\"") and line.endswith("\""):
  138. last_source[current_state] += line + "\n"
  139. else: #White lines trigger us to process this translation. Is it the correct one?
  140. #Process the source and destination keys for comparison independent of newline technique.
  141. source_ctxt = "".join((line.strip()[1:-1] for line in last_source["msgctxt"].split("\n")))
  142. source_id = "".join((line.strip()[1:-1] for line in last_source["msgid"].split("\n")))
  143. dest_ctxt = "".join((line.strip()[1:-1] for line in msgctxt.split("\n")))
  144. dest_id = "".join((line.strip()[1:-1] for line in msgid.split("\n")))
  145. if source_ctxt == dest_ctxt and source_id == dest_id:
  146. if last_source["msgstr"] == "\"\"\n" and last_source["msgid_plural"] == "\"\"\n":
  147. print("!!! Empty translation for {" + dest_ctxt + "}", dest_id, "!!!")
  148. return last_source["msgstr"]
  149. last_source = {
  150. "msgctxt": "\"\"\n",
  151. "msgid": "\"\"\n",
  152. "msgstr": "\"\"\n",
  153. "msgid_plural": "\"\"\n"
  154. }
  155. #Still here? Then the entire msgctxt+msgid combination was not found at all.
  156. print("!!! Missing translation for {" + msgctxt.strip() + "}", msgid.strip(), "!!!")
  157. return "\"\"\n"
  158. if __name__ == "__main__":
  159. argparser = argparse.ArgumentParser(description = "Import translation files from Lionbridge.")
  160. argparser.add_argument("source")
  161. args = argparser.parse_args()
  162. lionbridge_import(args.source)