diff --git a/backend/alembic/versions/f1a2b3c4d5e6_drop_dict_dialect_columns.py b/backend/alembic/versions/f1a2b3c4d5e6_drop_dict_dialect_columns.py new file mode 100644 index 00000000..8b40791a --- /dev/null +++ b/backend/alembic/versions/f1a2b3c4d5e6_drop_dict_dialect_columns.py @@ -0,0 +1,44 @@ +"""drop source_dialect/target_dialect from terminology_dictionaries + +Revision ID: f1a2b3c4d5e6 +Revises: dabc97097e0e +Create Date: 2026-06-02 12:00:00.000000 + +""" +from collections.abc import Sequence + +from alembic import op +from sqlalchemy import inspect + + +# revision identifiers, used by Alembic. +revision: str = 'f1a2b3c4d5e6' +down_revision: str | Sequence[str] | None = 'dabc97097e0e' +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def _column_exists(table_name: str, column_name: str) -> bool: + conn = op.get_bind() + inspector = inspect(conn) + columns = [c["name"] for c in inspector.get_columns(table_name)] + return column_name in columns + + +def upgrade() -> None: + """Drop source_dialect and target_dialect from terminology_dictionaries.""" + if not _column_exists("terminology_dictionaries", "source_dialect"): + return + op.drop_column("terminology_dictionaries", "source_dialect") + op.drop_column("terminology_dictionaries", "target_dialect") + + +def downgrade() -> None: + """Re-add source_dialect and target_dialect to terminology_dictionaries.""" + import sqlalchemy as sa + op.add_column("terminology_dictionaries", sa.Column( + "source_dialect", sa.String(), nullable=False, server_default="", + )) + op.add_column("terminology_dictionaries", sa.Column( + "target_dialect", sa.String(), nullable=False, server_default="", + )) diff --git a/backend/src/api/routes/settings.py b/backend/src/api/routes/settings.py index 8c6cfcf8..b9840aa9 100755 --- a/backend/src/api/routes/settings.py +++ b/backend/src/api/routes/settings.py @@ -128,6 +128,22 @@ async def get_features( # #endregion get_features +# #region get_allowed_languages [C:1] [TYPE Function] +# @BRIEF Public endpoint returning allowed translation languages for language dropdowns. +# @RATIONALE No auth required — needed by dictionary page, job config, etc. everywhere +# language selects appear. Non-admin users still need the list. +# @PRE Config manager is available. +# @POST Returns list of BCP-47 language codes. +@router.get("/allowed-languages") +async def get_allowed_languages( + config_manager: ConfigManager = Depends(get_config_manager), +): + return config_manager.get_config().settings.allowed_languages + + +# #endregion get_allowed_languages + + # #region update_global_settings [C:2] [TYPE Function] # @BRIEF Updates global application settings. # @PRE New settings are provided. @@ -394,6 +410,7 @@ class ConsolidatedSettingsResponse(BaseModel): notifications: dict = {} features: dict = {} app_timezone: str = "Europe/Moscow" + allowed_languages: list[str] = [] # #endregion ConsolidatedSettingsResponse @@ -465,6 +482,7 @@ async def get_consolidated_settings( notifications=notifications_payload, features=config.settings.features.model_dump(), app_timezone=config.settings.app_timezone, + allowed_languages=config.settings.allowed_languages, ) logger.reflect( "Consolidated settings payload assembled", @@ -537,6 +555,10 @@ async def update_consolidated_settings( current_settings.app_timezone = new_tz invalidate_timezone_cache() + # Update allowed_languages if provided + if "allowed_languages" in settings_patch: + current_settings.allowed_languages = settings_patch["allowed_languages"] + config_manager.update_global_settings(current_settings) return {"status": "success", "message": "Settings updated"} diff --git a/backend/src/api/routes/translate/_dictionary_routes.py b/backend/src/api/routes/translate/_dictionary_routes.py index 2c483447..754d8a4d 100644 --- a/backend/src/api/routes/translate/_dictionary_routes.py +++ b/backend/src/api/routes/translate/_dictionary_routes.py @@ -88,8 +88,6 @@ async def create_dictionary( d = DictionaryManager.create_dictionary( db, name=payload.name, - source_dialect=payload.source_dialect, - target_dialect=payload.target_dialect, created_by=current_user.username, description=payload.description, is_active=payload.is_active, @@ -120,8 +118,6 @@ async def update_dictionary( dict_id=dictionary_id, name=payload.name, description=payload.description, - source_dialect=payload.source_dialect, - target_dialect=payload.target_dialect, is_active=payload.is_active, ) from ....models.translate import DictionaryEntry diff --git a/backend/src/api/routes/translate/_helpers.py b/backend/src/api/routes/translate/_helpers.py index 3c843e52..a99a968e 100644 --- a/backend/src/api/routes/translate/_helpers.py +++ b/backend/src/api/routes/translate/_helpers.py @@ -43,8 +43,6 @@ def _dict_to_response(d: Any, entry_count: int = 0) -> dict: "id": d.id, "name": d.name, "description": d.description, - "source_dialect": d.source_dialect, - "target_dialect": d.target_dialect, "is_active": d.is_active, "created_by": d.created_by, "created_at": d.created_at, diff --git a/backend/src/core/config_models.py b/backend/src/core/config_models.py index b7d73c87..33179fd5 100755 --- a/backend/src/core/config_models.py +++ b/backend/src/core/config_models.py @@ -141,6 +141,26 @@ class GlobalSettings(BaseModel): # Global worker limit for concurrent validation runs GLOBAL_VALIDATION_WORKER_LIMIT: int = 3 + # Allowed languages for translation (BCP-47 codes) + allowed_languages: list[str] = Field( + default_factory=lambda: [ + "ru", "en", "de", "fr", "es", "it", "pt", "zh", "ja", "ko", + "ar", "tr", "nl", "pl", "sv", "da", "fi", "cs", "hu", "ro", + "vi", "th", "he", "id", "ms", + ] + ) + + @field_validator("allowed_languages") + @classmethod + def validate_allowed_languages(cls, v: list[str]) -> list[str]: + import re + for tag in v: + if not tag or not tag.strip(): + raise ValueError("Empty language code is not allowed") + if not re.match(r'^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$', tag.strip()): + raise ValueError(f"Invalid BCP-47 language code: {tag}") + return v + # #endregion GlobalSettings diff --git a/backend/src/models/translate.py b/backend/src/models/translate.py index 74c91440..b524cf01 100644 --- a/backend/src/models/translate.py +++ b/backend/src/models/translate.py @@ -205,8 +205,6 @@ class TerminologyDictionary(Base): id = Column(String, primary_key=True, default=generate_uuid) name = Column(String, nullable=False) description = Column(Text, nullable=True) - source_dialect = Column(String, nullable=False) - target_dialect = Column(String, nullable=False) is_active = Column(Boolean, default=True) created_by = Column(String, nullable=True) created_at = Column(DateTime, default=lambda: datetime.now(UTC)) diff --git a/backend/src/plugins/translate/__tests__/test_dict_snapshot_hash.py b/backend/src/plugins/translate/__tests__/test_dict_snapshot_hash.py index 08ddcc7d..6d846d23 100644 --- a/backend/src/plugins/translate/__tests__/test_dict_snapshot_hash.py +++ b/backend/src/plugins/translate/__tests__/test_dict_snapshot_hash.py @@ -63,8 +63,6 @@ def _create_dict(db_session, name="Test Dict") -> TerminologyDictionary: d = TerminologyDictionary( id=str(uuid.uuid4()), name=name, - source_dialect="ru", - target_dialect="en", ) db_session.add(d) db_session.flush() diff --git a/backend/src/plugins/translate/__tests__/test_dictionary_correction.py b/backend/src/plugins/translate/__tests__/test_dictionary_correction.py index a23a0b5e..42c7bf8c 100644 --- a/backend/src/plugins/translate/__tests__/test_dictionary_correction.py +++ b/backend/src/plugins/translate/__tests__/test_dictionary_correction.py @@ -44,7 +44,7 @@ class TestContextCapture: # region test_context_capture_in_correction [C:2] [TYPE Function] def test_context_capture_in_correction(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test Dict", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test Dict") job = TranslationJob(name="Ctx Job", source_dialect="a", target_dialect="b", status="DRAFT") db_session.add(job) db_session.flush() diff --git a/backend/src/plugins/translate/__tests__/test_dictionary_crud.py b/backend/src/plugins/translate/__tests__/test_dictionary_crud.py index a748d7fb..ea007fd8 100644 --- a/backend/src/plugins/translate/__tests__/test_dictionary_crud.py +++ b/backend/src/plugins/translate/__tests__/test_dictionary_crud.py @@ -46,13 +46,10 @@ class TestDictionaryCRUD: def test_create_dictionary(self, db_session: Session): d = DictionaryManager.create_dictionary( db_session, name="Finance Terms", - source_dialect="postgresql", target_dialect="clickhouse", created_by="test_user", description="Finance-related term mappings", ) assert d.id is not None assert d.name == "Finance Terms" - assert d.source_dialect == "postgresql" - assert d.target_dialect == "clickhouse" assert d.created_by == "test_user" assert d.is_active is True @@ -64,7 +61,7 @@ class TestDictionaryCRUD: # region test_update_dictionary [C:2] [TYPE Function] # @BRIEF Verify dictionary metadata update. def test_update_dictionary(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Old Name", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Old Name") updated = DictionaryManager.update_dictionary( db_session, d.id, name="New Name", description="Updated desc", is_active=False, ) @@ -76,7 +73,7 @@ class TestDictionaryCRUD: # region test_delete_dictionary [C:2] [TYPE Function] # @BRIEF Verify dictionary deletion also removes entries. def test_delete_dictionary(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="To Delete", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="To Delete") entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") assert entry.id is not None DictionaryManager.delete_dictionary(db_session, d.id) @@ -88,7 +85,7 @@ class TestDictionaryCRUD: # @BRIEF Verify paginated dictionary listing. def test_list_dictionaries(self, db_session: Session): for i in range(5): - DictionaryManager.create_dictionary(db_session, name=f"Dict {i}", source_dialect="a", target_dialect="b") + DictionaryManager.create_dictionary(db_session, name=f"Dict {i}") dicts, total = DictionaryManager.list_dictionaries(db_session, page=1, page_size=2) assert total == 5 assert len(dicts) == 2 @@ -97,7 +94,7 @@ class TestDictionaryCRUD: # region test_delete_dictionary_blocked_by_active_job [C:2] [TYPE Function] # @BRIEF Verify deletion is blocked when attached to active/scheduled jobs. def test_delete_dictionary_blocked_by_active_job(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") job = TranslationJob(name="Active Job", source_dialect="a", target_dialect="b", status="ACTIVE", created_by="test_user") db_session.add(job) db_session.flush() @@ -111,7 +108,7 @@ class TestDictionaryCRUD: # region test_delete_dictionary_allowed_with_completed_job [C:2] [TYPE Function] # @BRIEF Verify deletion is allowed when only completed/failed jobs reference the dictionary. def test_delete_dictionary_allowed_with_completed_job(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") job = TranslationJob(name="Completed Job", source_dialect="a", target_dialect="b", status="COMPLETED", created_by="test_user") db_session.add(job) db_session.flush() @@ -130,7 +127,7 @@ class TestDictionaryEntryCRUD: # region test_add_entry_duplicate [C:2] [TYPE Function] # @BRIEF Verify duplicate entry raises ValueError. def test_add_entry_duplicate(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "Hello", "Hola", source_language="en", target_language="es") with pytest.raises(ValueError, match="already exists"): DictionaryManager.add_entry(db_session, d.id, "hello", "Bonjour", source_language="en", target_language="es") @@ -141,8 +138,8 @@ class TestDictionaryEntryCRUD: # region test_add_entry_duplicate_per_dictionary [C:2] [TYPE Function] # @BRIEF Verify duplicate is per-dictionary (same term in different dicts is OK). def test_add_entry_duplicate_per_dictionary(self, db_session: Session): - d1 = DictionaryManager.create_dictionary(db_session, name="Dict1", source_dialect="a", target_dialect="b") - d2 = DictionaryManager.create_dictionary(db_session, name="Dict2", source_dialect="a", target_dialect="b") + d1 = DictionaryManager.create_dictionary(db_session, name="Dict1") + d2 = DictionaryManager.create_dictionary(db_session, name="Dict2") DictionaryManager.add_entry(db_session, d1.id, "hello", "hola", source_language="en", target_language="es") entry = DictionaryManager.add_entry(db_session, d2.id, "hello", "bonjour", source_language="en", target_language="es") assert entry.id is not None @@ -151,7 +148,7 @@ class TestDictionaryEntryCRUD: # region test_edit_entry [C:2] [TYPE Function] # @BRIEF Verify entry edit updates fields and enforces uniqueness. def test_edit_entry(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") updated = DictionaryManager.edit_entry(db_session, entry.id, target_term="HOLA!") assert updated.target_term == "HOLA!" @@ -163,7 +160,7 @@ class TestDictionaryEntryCRUD: # region test_delete_entry [C:2] [TYPE Function] # @BRIEF Verify entry deletion. def test_delete_entry(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") DictionaryManager.delete_entry(db_session, entry.id) entries, total = DictionaryManager.list_entries(db_session, d.id) @@ -173,7 +170,7 @@ class TestDictionaryEntryCRUD: # region test_clear_entries [C:2] [TYPE Function] # @BRIEF Verify clearing all entries for a dictionary. def test_clear_entries(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") DictionaryManager.add_entry(db_session, d.id, "world", "mundo", source_language="en", target_language="es") deleted = DictionaryManager.clear_entries(db_session, d.id) @@ -185,7 +182,7 @@ class TestDictionaryEntryCRUD: # region test_add_entry_with_language_pair [C:2] [TYPE Function] # @BRIEF Verify creating entry with language pair stores correctly. def test_add_entry_with_language_pair(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Lang Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Lang Test") entry = DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") assert entry.source_language == "en" assert entry.target_language == "ru" @@ -198,7 +195,7 @@ class TestDictionaryEntryCRUD: # region test_duplicate_same_language_pair [C:2] [TYPE Function] # @BRIEF Verify duplicate with same language pair raises conflict. def test_duplicate_same_language_pair(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Dup Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Dup Test") DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") with pytest.raises(ValueError, match="already exists"): DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="ru") @@ -207,7 +204,7 @@ class TestDictionaryEntryCRUD: # region test_same_term_different_language_pair [C:2] [TYPE Function] # @BRIEF Verify same term with different language pair is allowed. def test_same_term_different_language_pair(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Multi Lang", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Multi Lang") entry1 = DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") entry2 = DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="de") assert entry1.id != entry2.id diff --git a/backend/src/plugins/translate/__tests__/test_dictionary_filter.py b/backend/src/plugins/translate/__tests__/test_dictionary_filter.py index 98bd8aff..b5452eab 100644 --- a/backend/src/plugins/translate/__tests__/test_dictionary_filter.py +++ b/backend/src/plugins/translate/__tests__/test_dictionary_filter.py @@ -50,7 +50,7 @@ class TestFilterBasic: # region test_filter_for_batch_matches [C:2] [TYPE Function] def test_filter_for_batch_matches(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test Dict", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test Dict") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") DictionaryManager.add_entry(db_session, d.id, "world", "mundo", source_language="en", target_language="es") DictionaryManager.add_entry(db_session, d.id, "foo", "bar", source_language="en", target_language="es") @@ -76,7 +76,7 @@ class TestFilterBasic: # region test_filter_for_batch_case_insensitive [C:2] [TYPE Function] def test_filter_for_batch_case_insensitive(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test Dict", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test Dict") DictionaryManager.add_entry(db_session, d.id, "Hello World", "Hola Mundo", source_language="en", target_language="es") job = TranslationJob(name="Test Job", source_dialect="a", target_dialect="b", status="DRAFT") @@ -94,7 +94,7 @@ class TestFilterBasic: # region test_filter_for_batch_word_boundary [C:2] [TYPE Function] def test_filter_for_batch_word_boundary(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test Dict", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test Dict") DictionaryManager.add_entry(db_session, d.id, "cat", "gato", source_language="en", target_language="es") job = TranslationJob(name="Test Job", source_dialect="a", target_dialect="b", status="DRAFT") @@ -117,8 +117,8 @@ class TestFilterPriority: # region test_filter_for_batch_multi_dictionary_priority [C:2] [TYPE Function] def test_filter_for_batch_multi_dictionary_priority(self, db_session: Session): - d1 = DictionaryManager.create_dictionary(db_session, name="Priority1", source_dialect="a", target_dialect="b") - d2 = DictionaryManager.create_dictionary(db_session, name="Priority2", source_dialect="a", target_dialect="b") + d1 = DictionaryManager.create_dictionary(db_session, name="Priority1") + d2 = DictionaryManager.create_dictionary(db_session, name="Priority2") DictionaryManager.add_entry(db_session, d1.id, "hello", "hola", source_language="en", target_language="es") DictionaryManager.add_entry(db_session, d2.id, "hello", "bonjour", source_language="en", target_language="es") @@ -145,7 +145,7 @@ class TestFilterLanguagePair: # region test_filter_for_batch_with_language_pair [C:2] [TYPE Function] def test_filter_for_batch_with_language_pair(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Lang Filter", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Lang Filter") DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") DictionaryManager.add_entry(db_session, d.id, "world", "мир", source_language="en", target_language="ru") DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="de") @@ -169,7 +169,7 @@ class TestFilterLanguagePair: # region test_filter_for_batch_target_language_only [C:2] [TYPE Function] def test_filter_for_batch_target_language_only(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Target Only", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Target Only") DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="de") DictionaryManager.add_entry(db_session, d.id, "bonjour", "hallo", source_language="fr", target_language="de") @@ -196,7 +196,7 @@ class TestMigration: # region test_migrate_old_entries [C:2] [TYPE Function] def test_migrate_old_entries(self, db_session: Session): - d = TerminologyDictionary(name="Old Dict", source_dialect="a", target_dialect="b") + d = TerminologyDictionary(name="Old Dict") db_session.add(d) db_session.flush() diff --git a/backend/src/plugins/translate/__tests__/test_dictionary_import.py b/backend/src/plugins/translate/__tests__/test_dictionary_import.py index 784af0e4..02497064 100644 --- a/backend/src/plugins/translate/__tests__/test_dictionary_import.py +++ b/backend/src/plugins/translate/__tests__/test_dictionary_import.py @@ -38,7 +38,7 @@ class TestImportOverwrite: # region test_import_csv_overwrite [C:2] [TYPE Function] def test_import_csv_overwrite(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") csv_content = "source_term,target_term,context_notes,source_language,target_language\nhello,HELLO,,en,es\nworld,mundo,,en,es" @@ -59,7 +59,7 @@ class TestImportKeepExisting: # region test_import_csv_keep_existing [C:2] [TYPE Function] def test_import_csv_keep_existing(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") csv_content = "source_term,target_term,source_language,target_language\nhello,HELLO,en,es\nworld,mundo,en,es" @@ -79,7 +79,7 @@ class TestImportCancel: # region test_import_csv_cancel_on_conflict [C:2] [TYPE Function] def test_import_csv_cancel_on_conflict(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") csv_content = "source_term,target_term,source_language,target_language\nhello,HELLO,en,es\nworld,mundo,en,es" @@ -97,7 +97,7 @@ class TestImportFormat: # region test_import_tsv [C:2] [TYPE Function] def test_import_tsv(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") tsv_content = "source_term\ttarget_term\tsource_language\ttarget_language\nhello\thola\ten\tes\nworld\tmundo\ten\tes" result = DictionaryManager.import_entries(db_session, d.id, tsv_content, delimiter="\t", on_conflict="overwrite") assert result["created"] == 2 @@ -106,14 +106,14 @@ class TestImportFormat: # region test_import_invalid_format [C:2] [TYPE Function] def test_import_invalid_format(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") with pytest.raises(ValueError, match="source_term"): DictionaryManager.import_entries(db_session, d.id, "name,value\nhello,hola", delimiter=",", on_conflict="overwrite") # endregion test_import_invalid_format # region test_import_empty_rows [C:2] [TYPE Function] def test_import_empty_rows(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") csv_content = "source_term,target_term\nhello,hola\n,world\nfoo," result = DictionaryManager.import_entries(db_session, d.id, csv_content, delimiter=",", on_conflict="overwrite") assert result["created"] == 1 @@ -122,7 +122,7 @@ class TestImportFormat: # region test_import_preview [C:2] [TYPE Function] def test_import_preview(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Test") DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es") csv_content = "source_term,target_term,source_language,target_language\nhello,HELLO,en,es\nworld,mundo,en,es" @@ -142,7 +142,7 @@ class TestExportEntries: # region test_export_entries [C:2] [TYPE Function] def test_export_entries(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Export Test", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Export Test") DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru") DictionaryManager.add_entry(db_session, d.id, "world", "мир", source_language="en", target_language="ru") @@ -161,7 +161,7 @@ class TestExportEntries: # region test_import_with_default_language [C:2] [TYPE Function] def test_import_with_default_language(self, db_session: Session): - d = DictionaryManager.create_dictionary(db_session, name="Default Lang Import", source_dialect="a", target_dialect="b") + d = DictionaryManager.create_dictionary(db_session, name="Default Lang Import") csv_content = "source_term,target_term\nhello,привет\nworld,мир" result = DictionaryManager.import_entries( db_session, d.id, csv_content, delimiter=",", on_conflict="overwrite", diff --git a/backend/src/plugins/translate/dictionary_correction.py b/backend/src/plugins/translate/dictionary_correction.py index 38e48c03..005cd9ce 100644 --- a/backend/src/plugins/translate/dictionary_correction.py +++ b/backend/src/plugins/translate/dictionary_correction.py @@ -45,8 +45,8 @@ class DictionaryCorrectionService: effective_context_source = "manual" normalized = _normalize_term(source_term) - entry_src_lang = dictionary.source_dialect or "und" - entry_tgt_lang = dictionary.target_dialect or "und" + entry_src_lang = "und" + entry_tgt_lang = "und" existing = ( db.query(DictionaryEntry) @@ -139,8 +139,8 @@ class DictionaryCorrectionService: continue normalized = _normalize_term(source_term) - bulk_src_lang = dictionary.source_dialect or "und" - bulk_tgt_lang = dictionary.target_dialect or "und" + bulk_src_lang = "und" + bulk_tgt_lang = "und" existing = ( db.query(DictionaryEntry) diff --git a/backend/src/plugins/translate/dictionary_crud.py b/backend/src/plugins/translate/dictionary_crud.py index 0b87645b..f8170ae1 100644 --- a/backend/src/plugins/translate/dictionary_crud.py +++ b/backend/src/plugins/translate/dictionary_crud.py @@ -24,15 +24,13 @@ class DictionaryCRUD: @staticmethod def create_dictionary( db: Session, name: str, - source_dialect: str = "", target_dialect: str = "", created_by: str | None = None, description: str | None = None, is_active: bool = True, ) -> TerminologyDictionary: with belief_scope("DictionaryCRUD.create_dictionary"): - logger.reason("Creating dictionary", {"name": name, "source": source_dialect, "target": target_dialect}) + logger.reason("Creating dictionary", {"name": name}) dictionary = TerminologyDictionary( name=name, description=description, - source_dialect=source_dialect or "", target_dialect=target_dialect or "", is_active=is_active, created_by=created_by, ) db.add(dictionary) @@ -47,8 +45,7 @@ class DictionaryCRUD: @staticmethod def update_dictionary( db: Session, dict_id: str, name: str | None = None, - description: str | None = None, source_dialect: str | None = None, - target_dialect: str | None = None, is_active: bool | None = None, + description: str | None = None, is_active: bool | None = None, ) -> TerminologyDictionary: with belief_scope("DictionaryCRUD.update_dictionary"): dictionary = db.query(TerminologyDictionary).filter(TerminologyDictionary.id == dict_id).first() @@ -59,10 +56,6 @@ class DictionaryCRUD: dictionary.name = name if description is not None: dictionary.description = description - if source_dialect is not None: - dictionary.source_dialect = source_dialect - if target_dialect is not None: - dictionary.target_dialect = target_dialect if is_active is not None: dictionary.is_active = is_active db.commit() diff --git a/backend/src/schemas/translate.py b/backend/src/schemas/translate.py index 707f179a..eeabb42a 100644 --- a/backend/src/schemas/translate.py +++ b/backend/src/schemas/translate.py @@ -173,8 +173,6 @@ class DuplicateJobResponse(BaseModel): class DictionaryCreate(BaseModel): name: str description: str | None = None - source_dialect: str - target_dialect: str is_active: bool = True # #endregion DictionaryCreate @@ -197,8 +195,6 @@ class DictionaryResponse(BaseModel): id: str name: str description: str | None = None - source_dialect: str - target_dialect: str is_active: bool created_by: str | None = None created_at: datetime diff --git a/backend/tests/test_translate_corrections.py b/backend/tests/test_translate_corrections.py index 62d38864..aced4c29 100644 --- a/backend/tests/test_translate_corrections.py +++ b/backend/tests/test_translate_corrections.py @@ -105,7 +105,7 @@ def client(mock_api_deps): def test_submit_correction_creates_entry(db_session): """Test that submitting a correction creates a new entry.""" dict_obj = DictionaryManager.create_dictionary( - db_session, name="Test Dict", source_dialect="en", target_dialect="ru", + db_session, name="Test Dict", created_by="testuser", ) @@ -138,7 +138,7 @@ def test_submit_correction_creates_entry(db_session): def test_submit_correction_conflict_detected(db_session): """Test that conflict is detected when entry already exists.""" dict_obj = DictionaryManager.create_dictionary( - db_session, name="Dict", source_dialect="en", target_dialect="ru", + db_session, name="Dict", ) # Create initial entry @@ -165,7 +165,7 @@ def test_submit_correction_conflict_detected(db_session): def test_submit_correction_overwrite(db_session): """Test that correction overwrites existing entry.""" dict_obj = DictionaryManager.create_dictionary( - db_session, name="Dict", source_dialect="en", target_dialect="ru", + db_session, name="Dict", ) DictionaryManager.add_entry(db_session, dict_obj.id, "hello", "privet") @@ -190,7 +190,7 @@ def test_submit_correction_overwrite(db_session): def test_bulk_corrections_atomic(db_session): """Test that bulk corrections are applied atomically.""" dict_obj = DictionaryManager.create_dictionary( - db_session, name="Bulk Dict", source_dialect="en", target_dialect="ru", + db_session, name="Bulk Dict", ) corrections = [ @@ -233,7 +233,7 @@ def test_api_bulk_corrections(client, mock_api_deps): """Test bulk corrections endpoint.""" session = mock_api_deps["session"] dict_obj = DictionaryManager.create_dictionary( - session, name="API Dict", source_dialect="en", target_dialect="ru", + session, name="API Dict", ) response = client.post("/api/translate/corrections/bulk", json={