diff --git a/dailystone/management/commands/translate_minerals.py b/dailystone/management/commands/translate_minerals.py new file mode 100644 index 0000000..1568059 --- /dev/null +++ b/dailystone/management/commands/translate_minerals.py @@ -0,0 +1,120 @@ +""" +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.' + )) diff --git a/dailystone/migrations/0003_mineral_properties_ru.py b/dailystone/migrations/0003_mineral_properties_ru.py new file mode 100644 index 0000000..8dcd0f1 --- /dev/null +++ b/dailystone/migrations/0003_mineral_properties_ru.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dailystone', '0002_add_russian_fields'), + ] + + operations = [ + migrations.AddField( + model_name='mineral', + name='properties_ru', + field=models.JSONField(blank=True, default=dict), + ), + ] diff --git a/dailystone/models.py b/dailystone/models.py index 198954a..17d0738 100644 --- a/dailystone/models.py +++ b/dailystone/models.py @@ -19,6 +19,7 @@ class Mineral(models.Model): day_of_year = models.IntegerField(unique=True, null=True, blank=True) # Russian translations + properties_ru = models.JSONField(default=dict, blank=True) name_ru = models.CharField(max_length=200, blank=True) description_ru = models.TextField(blank=True) history_ru = models.TextField(blank=True) diff --git a/dailystone/templates/dailystone/stone.html b/dailystone/templates/dailystone/stone.html index 2750abd..ed3a41f 100644 --- a/dailystone/templates/dailystone/stone.html +++ b/dailystone/templates/dailystone/stone.html @@ -533,16 +533,8 @@
- - {% if mineral.color_description %} - {{ mineral.color_description }} - {% else %} - Typical color - {% endif %} - - {% if mineral.color_description_ru %} - {{ mineral.color_description_ru }} - {% endif %} + {{ mineral.color_description|default:"Typical color" }} + {{ mineral.color_description_ru|default:mineral.properties_ru.color_description|default:mineral.color_description|default:"Типичный цвет" }}
{{ mineral.color_hex }}
@@ -554,13 +546,19 @@ {% if mineral.category %}
CategoryКатегория - {{ mineral.category }} + + {{ mineral.category }} + {{ mineral.properties_ru.category|default:mineral.category }} +
{% endif %} {% if mineral.crystal_system %}
Crystal SystemСингония - {{ mineral.crystal_system }} + + {{ mineral.crystal_system }} + {{ mineral.properties_ru.crystal_system|default:mineral.crystal_system }} +
{% endif %} {% if mineral.mohs_hardness %} @@ -572,19 +570,28 @@ {% if mineral.luster %}
LusterБлеск - {{ mineral.luster }} + + {{ mineral.luster }} + {{ mineral.properties_ru.luster|default:mineral.luster }} +
{% endif %} {% if mineral.streak %}
StreakЧерта - {{ mineral.streak }} + + {{ mineral.streak }} + {{ mineral.properties_ru.streak|default:mineral.streak }} +
{% endif %} {% if mineral.specific_gravity %}
Specific GravityПлотность - {{ mineral.specific_gravity }} + + {{ mineral.specific_gravity }} + {{ mineral.properties_ru.specific_gravity|default:mineral.specific_gravity }} +
{% endif %} diff --git a/requirements.txt b/requirements.txt index 4843fc0..d4664a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ gunicorn>=21.0 whitenoise>=6.6 requests>=2.31 beautifulsoup4>=4.12 +deep-translator>=1.11