lionbridge_import.py 8.5 KB

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