po_simplytranslate.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. try:
  3. from simplytranslate_engines.googletranslate import GoogleTranslateEngine
  4. from simplytranslate_engines.utils import to_lang_code
  5. except ModuleNotFoundError:
  6. print('You need to install SimplyTranslate-Engines. If you have no better idea, try:')
  7. print('pip install git+https://codeberg.org/SimpleWeb/SimplyTranslate-Engines')
  8. exit(1)
  9. import asyncio
  10. import argparse
  11. import io
  12. import os
  13. from pathlib import Path
  14. import polib
  15. import sys
  16. import textwrap
  17. import time
  18. # Available Translators from SimplyTranslate-Engines
  19. '''
  20. from simplytranslate_engines.libretranslate import LibreTranslateEngine
  21. from simplytranslate_engines.googletranslate import GoogleTranslateEngine
  22. from simplytranslate_engines.icibatranslate import IcibaTranslateEngine
  23. from simplytranslate_engines.deepl import DeeplEngine
  24. from simplytranslate_engines.reverso import ReversoTranslateEngine
  25. '''
  26. _my_name = os.path.basename(__file__)
  27. _my_input_default = 'my_strings.po'
  28. _my_output_default = 'auto_strings.po'
  29. DESCRIPTION = """
  30. You may need to install SimplyTranslate-Engines, e.g.
  31. > pip install git+https://codeberg.org/SimpleWeb/SimplyTranslate-Engines
  32. Opens the INFILE, assumed to be in 'english',
  33. and the needed language selected as --language
  34. Replace all (-r), or just fill in the missing translations ('msgstr':s),
  35. and save to OUTFILE
  36. """
  37. USAGE_EXAMPLE = f"""
  38. Examples:
  39. > ./{_my_name} -i input.pot -l danish -o output.da.po
  40. > ./{_my_name} -i input.po -l danish -o output.da.po --replace
  41. In case you did not get them all, or already have most of them:
  42. > ./{_my_name} -i test.da.po -l danish -o test.da.po
  43. """
  44. def parse_arguments():
  45. parser = argparse.ArgumentParser(_my_name,
  46. formatter_class=argparse.RawDescriptionHelpFormatter,
  47. description=textwrap.dedent(DESCRIPTION),
  48. epilog=textwrap.dedent(USAGE_EXAMPLE))
  49. add = parser.add_argument
  50. add('-r', '--replace', action='store_true',
  51. help='replace all translations (if using a .po file)')
  52. add('-q', '--quiet', action='store_true',
  53. help='be more quiet')
  54. add('-v', '--verbose', action='store_true',
  55. help='be more verbose')
  56. add('-l', '--language', metavar='LANGUAGE',
  57. help='select language')
  58. add('-i', '--input', metavar='INFILE',
  59. required=True,
  60. help='.po or .pot file for extraction')
  61. add('-o', '--output', metavar='OUTFILE',
  62. default=_my_output_default,
  63. help='output .po file with results')
  64. return parser.parse_args()
  65. def has_valid_text(the_string):
  66. if the_string.isspace():
  67. return False
  68. if len(the_string):
  69. return True
  70. return False
  71. def translate_gettext_file(file_name, language, options):
  72. '''
  73. Look for msgid's then translate their msgstr to language,
  74. note that these files are (expected to be) coded as UTF-8
  75. '''
  76. pofile = polib.pofile(file_name)
  77. translator = GoogleTranslateEngine()
  78. # Remember to add instance URL for LibreTranslateEngine, api key is optional
  79. # Reference: https://codeberg.org/SimpleWeb/SimplyTranslate-CLI
  80. '''
  81. api_key = None
  82. if instance is None:
  83. instance = "https://libretranslate.de"
  84. elif not (instance.startswith("https://") or instance.startswith("http://")):
  85. instance = f"https://{instance}"
  86. if api_key is None:
  87. translator = LibreTranslateEngine(instance)
  88. else:
  89. translator = LibreTranslateEngine(instance, api_key=api_key)
  90. '''
  91. from_language = 'english'
  92. to_language = language
  93. # Check if language is supported
  94. async def get_languages():
  95. return await asyncio.gather(
  96. asyncio.create_task(to_lang_code(from_language, translator)),
  97. asyncio.create_task(to_lang_code(to_language, translator))
  98. )
  99. from_language, to_language = asyncio.run(get_languages())
  100. translations = 0
  101. fails = 0
  102. result = 'FAIL'
  103. for entry in pofile:
  104. source = entry.msgid
  105. if options.verbose:
  106. print(f'In: "{source}"')
  107. else:
  108. print('.', end="")
  109. if not source:
  110. continue
  111. if not has_valid_text(source):
  112. result = source
  113. else:
  114. if not options.replace:
  115. if has_valid_text(entry.msgstr):
  116. result = entry.msgstr
  117. if options.verbose:
  118. print(f'Keeping: {result}\n')
  119. continue
  120. retries = 5
  121. untranslated = True
  122. while retries:
  123. try:
  124. proposal = translator.translate(source, from_language=from_language, to_language=to_language)
  125. result = asyncio.run(proposal)['translated-text']
  126. except:
  127. retries -= 1
  128. print(f'Exception, will try {retries} more after sleeping')
  129. time.sleep(1)
  130. result = ""
  131. else:
  132. untranslated = False
  133. translations += 1
  134. retries = 0
  135. if options.verbose:
  136. print(f'Got: {result}\n')
  137. if untranslated:
  138. fails += 1
  139. entry.msgstr = result
  140. if not options.verbose:
  141. print()
  142. print(f'Did {translations} translations')
  143. if fails:
  144. print(f'Left {fails} translations empty, rerun with -f')
  145. else:
  146. print('Everything is translated')
  147. if translations:
  148. pofile.save(options.output)
  149. else:
  150. print(' or did you forget --replace?')
  151. def main(options):
  152. # To be able to make debug-printout
  153. #sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
  154. #sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
  155. input_file_name = options.input
  156. if not os.path.exists(input_file_name):
  157. print(f'{options.input}, from the -i option, does not exist - giving up')
  158. exit(3)
  159. start = time.time()
  160. translate_gettext_file(input_file_name, options.language, options)
  161. end = time.time()
  162. print('Total time:', end - start, 's')
  163. if __name__ == '__main__':
  164. main(parse_arguments())