Files
k-boris-website/dailystone/management/commands/translate_minerals.py
Boris 0a3248daa4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add Russian translations for mineral property values
- Add properties_ru JSON field to store translated category, crystal_system,
  luster, streak, specific_gravity, color_description
- Add translate_minerals management command using deep-translator (Google
  Translate), with a hard-coded dictionary for crystal systems
- Template: show translated values in RU mode, fall back to English if missing
- Add deep-translator to requirements.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 08:56:23 +03:00

121 lines
4.2 KiB
Python

"""
Translate mineral property values (category, crystal_system, luster, streak,
specific_gravity, color_description) into Russian using Google Translate via
the deep-translator library. Results are stored in the properties_ru JSON field.
Usage:
python manage.py translate_minerals # translate all
python manage.py translate_minerals --skip-existing # skip already done
python manage.py translate_minerals --limit 50 # translate first 50
"""
import time
from django.core.management.base import BaseCommand
from dailystone.models import Mineral
# Hard-coded dictionary for standard mineralogical terms that translate
# inconsistently with machine translation.
CRYSTAL_SYSTEMS = {
'triclinic': 'Триклинная',
'monoclinic': 'Моноклинная',
'orthorhombic': 'Ромбическая',
'tetragonal': 'Тетрагональная',
'trigonal': 'Тригональная',
'hexagonal': 'Гексагональная',
'cubic': 'Кубическая',
'isometric': 'Кубическая',
'amorphous': 'Аморфная',
'rhombohedral': 'Ромбоэдрическая',
}
def _dict_translate(value, lookup):
"""Try a case-insensitive dictionary lookup, return None on miss."""
if not value:
return None
key = value.strip().lower()
for k, v in lookup.items():
if k in key:
return v
return None
class Command(BaseCommand):
help = 'Translate mineral property values to Russian using Google Translate'
def add_arguments(self, parser):
parser.add_argument('--limit', type=int, default=0,
help='Max minerals to process (0 = all)')
parser.add_argument('--skip-existing', action='store_true',
help='Skip minerals that already have properties_ru')
def handle(self, *args, **options):
try:
from deep_translator import GoogleTranslator
except ImportError:
self.stderr.write('deep-translator not installed. Run: pip install deep-translator')
return
translator = GoogleTranslator(source='en', target='ru')
qs = Mineral.objects.all()
if options['skip_existing']:
qs = qs.exclude(properties_ru__isnull=False).exclude(properties_ru={})
if options['limit']:
qs = qs[:options['limit']]
total = qs.count()
self.stdout.write(f'Translating properties for {total} minerals...')
done = 0
errors = 0
for mineral in qs:
props = dict(mineral.properties_ru or {})
changed = False
fields = {
'category': mineral.category,
'crystal_system': mineral.crystal_system,
'luster': mineral.luster,
'streak': mineral.streak,
'specific_gravity': mineral.specific_gravity,
'color_description': mineral.color_description_ru or mineral.color_description,
}
for key, value in fields.items():
if not value or key in props:
continue
# Try dictionary first for crystal system
if key == 'crystal_system':
result = _dict_translate(value, CRYSTAL_SYSTEMS)
if result:
props[key] = result
changed = True
continue
# Machine translate
try:
translated = translator.translate(value)
if translated and translated != value:
props[key] = translated
changed = True
time.sleep(0.3)
except Exception as e:
self.stderr.write(f' [{mineral.name}] {key}: {e}')
errors += 1
time.sleep(1)
if changed:
mineral.properties_ru = props
mineral.save(update_fields=['properties_ru'])
done += 1
self.stdout.write(f' [{done}/{total}] {mineral.name}')
self.stdout.write(self.style.SUCCESS(
f'Done. Translated {done} minerals, {errors} errors.'
))