Skip to content

Commit a87d7cc

Browse files
authored
Merge pull request #16 from UniversalPython/add-more-advanced-translations
feat: translate variables and function names to target language
2 parents ac80ebc + 544e305 commit a87d7cc

6 files changed

Lines changed: 149 additions & 50 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
ply==3.11
22
PyYAML==5.4.1
33
Unidecode==1.3.2
4+
argostranslate==1.9.6

test/samples/de/hello.de.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
drucken("hello world")
1+
etwas = "hello world"
2+
drucken(etwas)
Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,91 @@
1-
def argos_translator(text, source):
2-
if not argos_translate_initialized:
3-
initialize_argos_translate()
4-
5-
if argos_translator is not None:
6-
try:
7-
return argos_translator.translate(text)
8-
except Exception as e:
9-
print(f"Warning: Translation failed - {str(e)}")
1+
import warnings
2+
from functools import lru_cache
3+
4+
# Global state variables
5+
_argos_translate_initialized = False
6+
_argos_translate_available = False
7+
_translation_models = {}
8+
9+
def initialize_argos_translate(source_lang="en", target_lang="en"):
10+
"""Initialize Argos Translate with status messages and package installation"""
11+
global _argos_translate_initialized, _argos_translate_available, _translation_models
12+
13+
if _argos_translate_initialized:
14+
return
15+
16+
_argos_translate_initialized = True
17+
18+
try:
19+
import argostranslate.package
20+
import argostranslate.translate
21+
_argos_translate_available = True
22+
23+
# Update package index
24+
print("Updating Argos Translate package index...")
25+
argostranslate.package.update_package_index()
26+
available_packages = argostranslate.package.get_available_packages()
27+
28+
# Find and install required translation package
29+
package_to_install = next(
30+
(pkg for pkg in available_packages
31+
if pkg.from_code == source_lang and pkg.to_code == target_lang),
32+
None
33+
)
34+
35+
if package_to_install:
36+
print(f"Installing translation package: {source_lang} -> {target_lang}")
37+
argostranslate.package.install_from_path(package_to_install.download())
38+
39+
# Load the installed model
40+
installed_languages = argostranslate.translate.get_installed_languages()
41+
from_lang = next((lang for lang in installed_languages if lang.code == source_lang), None)
42+
to_lang = next((lang for lang in installed_languages if lang.code == target_lang), None)
43+
44+
if from_lang and to_lang:
45+
_translation_models[(source_lang, target_lang)] = from_lang.get_translation(to_lang)
46+
print("Translation package installed successfully.")
47+
else:
48+
warnings.warn("Failed to load installed languages.")
49+
else:
50+
warnings.warn(f"No translation package available for {source_lang} -> {target_lang}")
51+
52+
except ImportError:
53+
warnings.warn("Argos Translate not available. Install with: pip install argostranslate")
54+
_argos_translate_available = False
55+
except Exception as e:
56+
warnings.warn(f"Failed to initialize Argos Translate: {str(e)}")
57+
_argos_translate_available = False
58+
59+
@lru_cache(maxsize=1000)
60+
def argos_translator(text, source_lang="en"):
61+
"""Translate text using Argos Translate with lazy initialization"""
62+
target_lang = "en" # We always translate to English
63+
64+
if not _argos_translate_initialized:
65+
initialize_argos_translate(source_lang, target_lang)
66+
67+
if not _argos_translate_available:
68+
return text
69+
70+
try:
71+
# Check if we have a cached model
72+
if (source_lang, target_lang) not in _translation_models:
73+
# Try to load existing installed model
74+
installed_languages = argostranslate.translate.get_installed_languages()
75+
from_lang = next((lang for lang in installed_languages if lang.code == source_lang), None)
76+
to_lang = next((lang for lang in installed_languages if lang.code == target_lang), None)
77+
78+
if from_lang and to_lang:
79+
_translation_models[(source_lang, target_lang)] = from_lang.get_translation(to_lang)
80+
else:
81+
warnings.warn(f"No translation model available for {source_lang} -> {target_lang}")
1082
return text
83+
84+
# Perform the translation
85+
translation_model = _translation_models[(source_lang, target_lang)]
86+
translated_text = translation_model.translate(text)
87+
return translated_text
88+
89+
except Exception as e:
90+
warnings.warn(f"Translation failed for '{text}': {str(e)}")
1191
return text

universalpython/modes/lex.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,15 @@ def t_ID(t):
271271
# print ("Value is:", t.value)
272272
# ------------- Debugging ---------------
273273

274-
275-
if args['translate']:
276-
# if args['translate'] == 'argostranslate':
277-
# if t.type == 'ID':
278-
# t.value = unidecode(t.value)
279-
# else:
280-
if t.type == 'ID':
281-
t.value = unidecode(t.value)
274+
if args['translate'] is not False:
275+
if args['translate'] == 'argostranslate':
276+
from universalpython.filters.translate.argos_translator import argos_translator
277+
if t.type == 'ID':
278+
# Only pass the source language, not the whole args dict
279+
t.value = argos_translator(t.value, args.get("source_language", "en"))
280+
else:
281+
if t.type == 'ID':
282+
t.value = unidecode(t.value)
282283
return t
283284

284285
if args["reverse"]:

universalpython/universalpython.py

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,55 +43,62 @@ def build_language_map():
4343
DEFAULT_LANGUAGE_MAP = build_language_map()
4444

4545
def detect_language_from_filename(filename):
46-
"""Detect language from file extension (e.g., my-program.de.py -> german)"""
46+
"""Detect language from file extension (e.g., my-program.de.py -> german)
47+
Returns tuple: (filepath, two_letter_code) or None"""
4748
parts = filename.split('.')
48-
49-
# ------------- Debugging ---------------
50-
# print("filename", filename)
51-
# print("parts", parts)
52-
# print("DEFAULT_LANGUAGE_MAP", DEFAULT_LANGUAGE_MAP)
53-
# ------------- Debugging ---------------
54-
49+
5550
if len(parts) > 2: # Has language code in extension
5651
lang_code = parts[-2].lower()
57-
return DEFAULT_LANGUAGE_MAP.get(lang_code, None)['default']
52+
if lang_code in DEFAULT_LANGUAGE_MAP:
53+
# Return first filepath found and the language code
54+
lang_files = DEFAULT_LANGUAGE_MAP[lang_code]
55+
first_file = next(iter(lang_files.values())) if lang_files else None
56+
return (first_file, lang_code)
5857
return None
5958

6059
def detect_language_from_comment(code):
61-
"""Detect language from comment (e.g., # language:fr)"""
60+
"""Detect language from comment (e.g., # language:fr)
61+
Returns tuple: (filepath, two_letter_code) or None"""
6262
first_lines = code.split('\n')[:5] # Check first 5 lines for comment
6363
for line in first_lines:
6464
match = re.search(r'^#\s*language\s*:\s*(\w+)', line, re.IGNORECASE)
6565
if match:
6666
lang_code = match.group(1).lower()
67-
return DEFAULT_LANGUAGE_MAP.get(lang_code, None)['default']
67+
if lang_code in DEFAULT_LANGUAGE_MAP:
68+
# Return first filepath found and the language code
69+
lang_files = DEFAULT_LANGUAGE_MAP[lang_code]
70+
first_file = next(iter(lang_files.values())) if lang_files else None
71+
return (first_file, lang_code)
6872
return None
6973

7074
def determine_language(args, filename, code):
7175
"""Determine target language based on priority rules"""
72-
# If dictionary is explicitly provided, use that
73-
if args.get('dictionary'):
74-
return args['dictionary']
76+
detected_dictionary = None
7577

76-
# Check for language comment (higher priority)
77-
lang = detect_language_from_comment(code)
78-
if lang:
79-
return lang
80-
81-
# Check file extension
82-
lang = detect_language_from_filename(filename)
83-
if lang:
84-
return lang
85-
86-
# Default to empty (no translation)
87-
return ""
78+
detected_lang = None
79+
80+
# Check detection methods in priority order
81+
if args.get('dictionary'):
82+
detected_dictionary = args['dictionary']
83+
elif args.get('source_language'):
84+
detected_dictionary = DEFAULT_LANGUAGE_MAP.get(args['source_language'], {})['default']
85+
else:
86+
detected_dictionary, detected_lang = detect_language_from_comment(code) or detect_language_from_filename(filename)
87+
88+
# Update source_language with the detected language if not explicitly set
89+
# if not args.get('source_language') and detected_lang:
90+
if detected_lang:
91+
args['source_language'] = detected_lang
92+
93+
return detected_dictionary or ""
8894

8995
def run_module(
9096
mode,
9197
code,
9298
args={
9399
'translate': False,
94100
'dictionary': "",
101+
'source_language': "",
95102
'reverse': False,
96103
'keep': False,
97104
'keep_only': False,
@@ -111,14 +118,23 @@ def main():
111118
help='File to compile.')
112119

113120
ap.add_argument("-t", "--translate",
114-
action='store_true',
115-
default=False, required=False,
116-
help="Translate variables and functions to English, using the unicode.")
121+
choices=["", "argostranslate", "unicode"],
122+
const="", # Default when --translate is used without value
123+
default=None, # Default when --translate is not used at all
124+
nargs='?', # Makes the argument optional
125+
required=False,
126+
help="Translate variables and functions. Options: "
127+
"no value (unicode), 'argostranslate', or 'unicode'")
117128

118129
ap.add_argument("-d", "--dictionary",
119130
default="", required=False,
120131
help="The dictionary to use to translate the code.")
121132

133+
ap.add_argument("-sl", "--source-language",
134+
default="", required=False,
135+
dest="source_language",
136+
help="The source language of the code (for translation).")
137+
122138
ap.add_argument("-r", "--reverse",
123139
action='store_true',
124140
default=False, required=False,
@@ -143,10 +159,10 @@ def main():
143159
args = vars(ap.parse_args())
144160

145161
filename = args["file"][0]
146-
code_pyfile = open(filename)
147-
code = code_pyfile.read()
162+
with open(filename) as code_pyfile:
163+
code = code_pyfile.read()
148164

149-
# Determine language if not explicitly provided
165+
# Determine language and update source_language
150166
args['dictionary'] = determine_language(args, filename, code)
151167

152168
# Default mode is 'lex' if not specified

0 commit comments

Comments
 (0)