"""Product Translation model for vendor-specific localized content. This model stores vendor-specific translations that can override the marketplace product translations. The override pattern works as follows: - NULL value = inherit from marketplace_product_translations - Non-NULL value = vendor-specific override This allows vendors to customize titles, descriptions, and SEO content per language while still being able to "reset to source" by setting values back to NULL. """ from sqlalchemy import ( Column, ForeignKey, Index, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.orm import relationship from app.core.database import Base from models.database.base import TimestampMixin class ProductTranslation(Base, TimestampMixin): """Vendor-specific localized content with override capability. Each vendor can have their own translations that override the marketplace product translations. Fields set to NULL inherit their value from the linked marketplace_product_translation. """ __tablename__ = "product_translations" id = Column(Integer, primary_key=True, index=True) product_id = Column( Integer, ForeignKey("products.id", ondelete="CASCADE"), nullable=False, ) language = Column(String(5), nullable=False) # 'en', 'fr', 'de', 'lb' # === OVERRIDABLE LOCALIZED FIELDS (NULL = inherit) === title = Column(String) description = Column(Text) short_description = Column(String(500)) # SEO Overrides meta_title = Column(String(70)) meta_description = Column(String(160)) url_slug = Column(String(255)) # === RELATIONSHIPS === product = relationship("Product", back_populates="translations") __table_args__ = ( UniqueConstraint("product_id", "language", name="uq_product_translation"), Index("idx_pt_product_id", "product_id"), Index("idx_pt_product_language", "product_id", "language"), ) # === OVERRIDABLE FIELDS LIST === OVERRIDABLE_FIELDS = [ "title", "description", "short_description", "meta_title", "meta_description", "url_slug", ] def __repr__(self): return ( f"" ) # === HELPER METHODS === def _get_marketplace_translation(self): """Get the corresponding marketplace translation for fallback.""" product = self.product if product and product.marketplace_product: mp = product.marketplace_product for t in mp.translations: if t.language == self.language: return t return None def _get_marketplace_translation_field(self, field: str): """Helper to get a field from marketplace translation.""" mp_translation = self._get_marketplace_translation() if mp_translation: return getattr(mp_translation, field, None) return None # === EFFECTIVE PROPERTIES (Override Pattern) === def get_effective_title(self) -> str | None: """Get title with fallback to marketplace translation.""" if self.title is not None: return self.title return self._get_marketplace_translation_field("title") def get_effective_description(self) -> str | None: """Get description with fallback to marketplace translation.""" if self.description is not None: return self.description return self._get_marketplace_translation_field("description") def get_effective_short_description(self) -> str | None: """Get short description with fallback to marketplace translation.""" if self.short_description is not None: return self.short_description return self._get_marketplace_translation_field("short_description") def get_effective_meta_title(self) -> str | None: """Get meta title with fallback to marketplace translation.""" if self.meta_title is not None: return self.meta_title return self._get_marketplace_translation_field("meta_title") def get_effective_meta_description(self) -> str | None: """Get meta description with fallback to marketplace translation.""" if self.meta_description is not None: return self.meta_description return self._get_marketplace_translation_field("meta_description") def get_effective_url_slug(self) -> str | None: """Get URL slug with fallback to marketplace translation.""" if self.url_slug is not None: return self.url_slug return self._get_marketplace_translation_field("url_slug") # === OVERRIDE INFO METHOD === def get_override_info(self) -> dict: """Get all fields with inheritance flags. Returns a dict with effective values and override flags. """ mp_translation = self._get_marketplace_translation() return { # Title "title": self.get_effective_title(), "title_overridden": self.title is not None, "title_source": mp_translation.title if mp_translation else None, # Description "description": self.get_effective_description(), "description_overridden": self.description is not None, "description_source": mp_translation.description if mp_translation else None, # Short Description "short_description": self.get_effective_short_description(), "short_description_overridden": self.short_description is not None, "short_description_source": ( mp_translation.short_description if mp_translation else None ), # Meta Title "meta_title": self.get_effective_meta_title(), "meta_title_overridden": self.meta_title is not None, "meta_title_source": mp_translation.meta_title if mp_translation else None, # Meta Description "meta_description": self.get_effective_meta_description(), "meta_description_overridden": self.meta_description is not None, "meta_description_source": ( mp_translation.meta_description if mp_translation else None ), # URL Slug "url_slug": self.get_effective_url_slug(), "url_slug_overridden": self.url_slug is not None, "url_slug_source": mp_translation.url_slug if mp_translation else None, } # === RESET METHODS === def reset_field_to_source(self, field_name: str) -> bool: """Reset a single field to inherit from marketplace translation. Args: field_name: Name of the field to reset Returns: True if field was reset, False if field is not overridable """ if field_name in self.OVERRIDABLE_FIELDS: setattr(self, field_name, None) return True return False def reset_to_source(self) -> None: """Reset all fields to inherit from marketplace translation.""" for field in self.OVERRIDABLE_FIELDS: setattr(self, field, None) def reset_fields_to_source(self, field_names: list[str]) -> list[str]: """Reset multiple fields to inherit from marketplace translation. Args: field_names: List of field names to reset Returns: List of fields that were successfully reset """ reset_fields = [] for field in field_names: if self.reset_field_to_source(field): reset_fields.append(field) return reset_fields