poesty 2 years ago
parent
commit
47169db3ec
4 changed files with 548 additions and 2 deletions
  1. 9 2
      README.md
  2. 177 0
      po_deep-translator.py
  3. 193 0
      po_simplytranslate.py
  4. 169 0
      po_translatepy.py

+ 9 - 2
README.md

@@ -1,3 +1,10 @@
 # po-translator
-
-Using multiple translators' lib to translate .po files.
+Using multiple translators' lib to translate .po files.
+# Reference
+* [po-autotranslate](https://github.com/NilssonOpel/po-autotranslate)
+* [translate-po](https://github.com/zcribe/translate-po)
+# Related lib
+* [translatepy](https://github.com/Animenosekai/translate)
+* [deep-translator](https://github.com/nidhaloff/deep-translator)
+* [SimplyTranslate-Engines](https://codeberg.org/SimpleWeb/SimplyTranslate-Engines)
+* ...

+ 177 - 0
po_deep-translator.py

@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+
+try:
+    from deep_translator import GoogleTranslator
+except ModuleNotFoundError:
+    print('You need to install deep-translator.  If you have no better idea, try:')
+    print('pip install deep-translator')
+    exit(1)
+import argparse
+import io
+import os
+from   pathlib import Path
+import polib
+import sys
+import textwrap
+import time
+
+# Available Translators from deep-translator
+'''
+from deep_translator import (GoogleTranslator, PonsTranslator,
+                             LingueeTranslator, MyMemoryTranslator,
+                             YandexTranslator, DeepL,
+                             QCRI, single_detection,
+                             batch_detection)
+'''
+
+_my_name = os.path.basename(__file__)
+_my_input_default = 'my_strings.po'
+_my_output_default = 'auto_strings.po'
+
+DESCRIPTION = """
+You may need to install translatepy, e.g.
+>  pip install translatepy
+
+Opens the INFILE, assumed to be in 'english',
+and the needed language selected as --language
+Replace all (-r), or just fill in the missing translations ('msgstr':s),
+and save to OUTFILE
+"""
+USAGE_EXAMPLE = f"""
+Examples:
+> ./{_my_name} -i input.pot -l danish -o output.da.po
+
+> ./{_my_name} -i input.po -l danish -o output.da.po --replace
+
+In case you did not get them all, or already have most of them:
+> ./{_my_name} -i test.da.po -l danish -o test.da.po
+"""
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(_my_name,
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=textwrap.dedent(DESCRIPTION),
+        epilog=textwrap.dedent(USAGE_EXAMPLE))
+    add = parser.add_argument
+    add('-r', '--replace', action='store_true',
+        help='replace all translations (if using a .po file)')
+    add('-q', '--quiet', action='store_true',
+        help='be more quiet')
+    add('-v', '--verbose', action='store_true',
+        help='be more verbose')
+    add('-l', '--language', metavar='LANGUAGE',
+        help='select language')
+    add('-i', '--input', metavar='INFILE',
+        required=True,
+        help='.po or .pot file for extraction')
+    add('-o', '--output', metavar='OUTFILE',
+        default=_my_output_default,
+        help='output .po file with results')
+
+    return parser.parse_args()
+
+
+def has_valid_text(the_string):
+    if the_string.isspace():
+        return False
+    if len(the_string):
+        return True
+    return False
+
+
+def translate_gettext_file(file_name, language, options):
+    '''
+    Look for msgid's then translate their msgstr to language,
+    note that these files are (expected to be) coded as UTF-8
+    '''
+    pofile = polib.pofile(file_name)
+
+    translator = GoogleTranslator(source='english', target=language)
+
+    # Check if language is supported -- pointless
+    '''
+    available_languages = translator.get_supported_languages(as_dict=True)
+    if not language in available_languages.values() or available_languages.keys():
+        print(f'Sorry, this imported translator do not speak {language}')
+        print(f'but they know')
+        for language_code,language in available_languages.items():
+            print('  ' +language+':'+language_code)
+        return
+    '''
+
+    translations = 0
+    fails = 0
+    result = 'FAIL'
+    for entry in pofile:
+        source = entry.msgid
+        if options.verbose:
+            print(f'In: "{source}"')
+        else:
+            print('.', end="")
+        if not source:
+            continue
+        if not has_valid_text(source):
+            result = source
+        else:
+            if not options.replace:
+                if has_valid_text(entry.msgstr):
+                    result = entry.msgstr
+                    if options.verbose:
+                        print(f'Keeping: {result}\n')
+                    continue
+            retries = 5
+            untranslated = True
+            while retries:
+                try:
+                    # number-only entry somehow can't be translated :(
+                    result = translator.translate(source)
+                except:
+                    retries -= 1
+                    print(f'Exception, will try {retries} more after sleeping')
+                    time.sleep(1)
+                    result = ""
+                else:
+                    untranslated = False
+                    translations += 1
+
+                    retries = 0
+            if options.verbose:
+                print(f'Got: {result}\n')
+
+            if untranslated:
+                fails += 1
+        entry.msgstr = result
+
+    if not options.verbose:
+        print()
+
+    print(f'Did {translations} translations')
+    if fails:
+        print(f'Left {fails} translations empty, rerun with -f')
+    else:
+        print('Everything is translated')
+    if translations:
+        pofile.save(options.output)
+    else:
+        print('  or did you forget --replace?')
+
+
+def main(options):
+    # To be able to make debug-printout
+    #sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
+    #sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
+
+    input_file_name = options.input
+    if not os.path.exists(input_file_name):
+        print(f'{options.input}, from the -i option, does not exist - giving up')
+        exit(3)
+
+    start = time.time()
+    translate_gettext_file(input_file_name, options.language, options)
+    end = time.time()
+    print('Total time:', end - start, 's')
+
+
+if __name__ == '__main__':
+    main(parse_arguments())

+ 193 - 0
po_simplytranslate.py

@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+
+try:
+    from simplytranslate_engines.googletranslate import GoogleTranslateEngine
+    from simplytranslate_engines.utils import to_lang_code
+except ModuleNotFoundError:
+    print('You need to install SimplyTranslate-Engines.  If you have no better idea, try:')
+    print('pip install git+https://codeberg.org/SimpleWeb/SimplyTranslate-Engines')
+    exit(1)
+import asyncio
+import argparse
+import io
+import os
+from   pathlib import Path
+import polib
+import sys
+import textwrap
+import time
+
+# Available Translators from SimplyTranslate-Engines
+'''
+from simplytranslate_engines.libretranslate import LibreTranslateEngine
+from simplytranslate_engines.googletranslate import GoogleTranslateEngine
+from simplytranslate_engines.icibatranslate import IcibaTranslateEngine
+from simplytranslate_engines.deepl import DeeplEngine
+from simplytranslate_engines.reverso import ReversoTranslateEngine
+'''
+
+_my_name = os.path.basename(__file__)
+_my_input_default = 'my_strings.po'
+_my_output_default = 'auto_strings.po'
+
+DESCRIPTION = """
+You may need to install translatepy, e.g.
+>  pip install translatepy
+
+Opens the INFILE, assumed to be in 'english',
+and the needed language selected as --language
+Replace all (-r), or just fill in the missing translations ('msgstr':s),
+and save to OUTFILE
+"""
+USAGE_EXAMPLE = f"""
+Examples:
+> ./{_my_name} -i input.pot -l danish -o output.da.po
+
+> ./{_my_name} -i input.po -l danish -o output.da.po --replace
+
+In case you did not get them all, or already have most of them:
+> ./{_my_name} -i test.da.po -l danish -o test.da.po
+"""
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(_my_name,
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=textwrap.dedent(DESCRIPTION),
+        epilog=textwrap.dedent(USAGE_EXAMPLE))
+    add = parser.add_argument
+    add('-r', '--replace', action='store_true',
+        help='replace all translations (if using a .po file)')
+    add('-q', '--quiet', action='store_true',
+        help='be more quiet')
+    add('-v', '--verbose', action='store_true',
+        help='be more verbose')
+    add('-l', '--language', metavar='LANGUAGE',
+        help='select language')
+    add('-i', '--input', metavar='INFILE',
+        required=True,
+        help='.po or .pot file for extraction')
+    add('-o', '--output', metavar='OUTFILE',
+        default=_my_output_default,
+        help='output .po file with results')
+
+    return parser.parse_args()
+
+
+def has_valid_text(the_string):
+    if the_string.isspace():
+        return False
+    if len(the_string):
+        return True
+    return False
+
+
+def translate_gettext_file(file_name, language, options):
+    '''
+    Look for msgid's then translate their msgstr to language,
+    note that these files are (expected to be) coded as UTF-8
+    '''
+    pofile = polib.pofile(file_name)
+
+    translator = GoogleTranslateEngine()
+    # Remember to add instance URL for LibreTranslateEngine, api key is optional
+    # Reference: https://codeberg.org/SimpleWeb/SimplyTranslate-CLI
+    '''
+     api_key = None
+     if instance is None:
+            instance = "https://libretranslate.de"
+        elif not (instance.startswith("https://") or instance.startswith("http://")):
+            instance = f"https://{instance}"
+
+        if api_key is None:
+            translator = LibreTranslateEngine(instance)
+        else:
+            translator = LibreTranslateEngine(instance, api_key=api_key)
+    '''
+
+    from_language = 'english'
+    to_language = language
+
+    # Check if language is supported
+    async def get_languages():
+        return await asyncio.gather(
+            asyncio.create_task(to_lang_code(from_language, translator)),
+            asyncio.create_task(to_lang_code(to_language, translator))
+        )
+    from_language, to_language = asyncio.run(get_languages())
+
+    translations = 0
+    fails = 0
+    result = 'FAIL'
+    for entry in pofile:
+        source = entry.msgid
+        if options.verbose:
+            print(f'In: "{source}"')
+        else:
+            print('.', end="")
+        if not source:
+            continue
+        if not has_valid_text(source):
+            result = source
+        else:
+            if not options.replace:
+                if has_valid_text(entry.msgstr):
+                    result = entry.msgstr
+                    if options.verbose:
+                        print(f'Keeping: {result}\n')
+                    continue
+            retries = 5
+            untranslated = True
+            while retries:
+                try:
+                    proposal = translator.translate(source, from_language=from_language, to_language=to_language)
+                    result = asyncio.run(proposal)['translated-text']
+                except:
+                    retries -= 1
+                    print(f'Exception, will try {retries} more after sleeping')
+                    time.sleep(1)
+                    result = ""
+                else:
+                    untranslated = False
+                    translations += 1
+
+                    retries = 0
+            if options.verbose:
+                print(f'Got: {result}\n')
+
+            if untranslated:
+                fails += 1
+        entry.msgstr = result
+
+    if not options.verbose:
+        print()
+
+    print(f'Did {translations} translations')
+    if fails:
+        print(f'Left {fails} translations empty, rerun with -f')
+    else:
+        print('Everything is translated')
+    if translations:
+        pofile.save(options.output)
+    else:
+        print('  or did you forget --replace?')
+
+
+def main(options):
+    # To be able to make debug-printout
+    #sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
+    #sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
+
+    input_file_name = options.input
+    if not os.path.exists(input_file_name):
+        print(f'{options.input}, from the -i option, does not exist - giving up')
+        exit(3)
+
+    start = time.time()
+    translate_gettext_file(input_file_name, options.language, options)
+    end = time.time()
+    print('Total time:', end - start, 's')
+
+
+if __name__ == '__main__':
+    main(parse_arguments())

+ 169 - 0
po_translatepy.py

@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+
+try:
+    from translatepy import Translator, Language
+except ModuleNotFoundError:
+    print('You need to install translatepy.  If you have no better idea, try:')
+    print('pip install translatepy')
+    exit(1)
+import argparse
+import io
+import os
+from   pathlib import Path
+import polib
+import sys
+import textwrap
+import time
+
+# Available Translators from translatepy
+from translatepy.translators import (BaseTranslator, BingTranslate,
+                                     DeeplTranslate, GoogleTranslate,
+                                     LibreTranslate, MyMemoryTranslate,
+                                     ReversoTranslate, TranslateComTranslate,
+                                     YandexTranslate, MicrosoftTranslate)
+
+_my_name = os.path.basename(__file__)
+_my_input_default = 'my_strings.po'
+_my_output_default = 'auto_strings.po'
+
+DESCRIPTION = """
+You may need to install translatepy, e.g.
+>  pip install translatepy
+
+Opens the INFILE, assumed to be in 'english',
+and the needed language selected as --language
+Replace all (-r), or just fill in the missing translations ('msgstr':s),
+and save to OUTFILE
+"""
+USAGE_EXAMPLE = f"""
+Examples:
+> ./{_my_name} -i input.pot -l danish -o output.da.po
+
+> ./{_my_name} -i input.po -l danish -o output.da.po --replace
+
+In case you did not get them all, or already have most of them:
+> ./{_my_name} -i test.da.po -l danish -o test.da.po
+"""
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(_my_name,
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=textwrap.dedent(DESCRIPTION),
+        epilog=textwrap.dedent(USAGE_EXAMPLE))
+    add = parser.add_argument
+    add('-r', '--replace', action='store_true',
+        help='replace all translations (if using a .po file)')
+    add('-q', '--quiet', action='store_true',
+        help='be more quiet')
+    add('-v', '--verbose', action='store_true',
+        help='be more verbose')
+    add('-l', '--language', metavar='LANGUAGE',
+        help='select language')
+    add('-i', '--input', metavar='INFILE',
+        required=True,
+        help='.po or .pot file for extraction')
+    add('-o', '--output', metavar='OUTFILE',
+        default=_my_output_default,
+        help='output .po file with results')
+
+    return parser.parse_args()
+
+
+def has_valid_text(the_string):
+    if the_string.isspace():
+        return False
+    if len(the_string):
+        return True
+    return False
+
+
+def translate_gettext_file(file_name, language, options):
+    '''
+    Look for msgid's then translate their msgstr to language,
+    note that these files are (expected to be) coded as UTF-8
+    '''
+    pofile = polib.pofile(file_name)
+
+    translator = Translator()
+    #translator = Translator([GoogleTranslate])
+
+    # Check if language is supported
+    assert Language(language)
+
+    translations = 0
+    fails = 0
+    result = 'FAIL'
+    for entry in pofile:
+        source = entry.msgid
+        if options.verbose:
+            print(f'In: "{source}"')
+        else:
+            print('.', end="")
+        if not source:
+            continue
+        if not has_valid_text(source):
+            result = source
+        else:
+            if not options.replace:
+                if has_valid_text(entry.msgstr):
+                    result = entry.msgstr
+                    if options.verbose:
+                        print(f'Keeping: {result}\n')
+                    continue
+            retries = 5
+            untranslated = True
+            while retries:
+                try:
+                    proposal = translator.translate(source, language, 'english')
+                    result = proposal.result
+                    translator = proposal.service
+                except:
+                    retries -= 1
+                    print(f'Exception, will try {retries} more after sleeping')
+                    time.sleep(1)
+                    result = ""
+                else:
+                    untranslated = False
+                    translations += 1
+
+                    retries = 0
+            if options.verbose:
+                print(f'Got: {result}\nTranslated by: {translator}\n')
+
+            if untranslated:
+                fails += 1
+        entry.msgstr = result
+
+    if not options.verbose:
+        print()
+
+    print(f'Did {translations} translations')
+    if fails:
+        print(f'Left {fails} translations empty, rerun with -f')
+    else:
+        print('Everything is translated')
+    if translations:
+        pofile.save(options.output)
+    else:
+        print('  or did you forget --replace?')
+
+
+def main(options):
+    # To be able to make debug-printout
+    #sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
+    #sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
+
+    input_file_name = options.input
+    if not os.path.exists(input_file_name):
+        print(f'{options.input}, from the -i option, does not exist - giving up')
+        exit(3)
+
+    start = time.time()
+    translate_gettext_file(input_file_name, options.language, options)
+    end = time.time()
+    print('Total time:', end - start, 's')
+
+
+if __name__ == '__main__':
+    main(parse_arguments())