# Translation Dictionary System - Implementation Plan **Version:** 1.0.0 **Date:** December 9, 2024 **Status:** PLANNING **Author:** Architecture Planning (Gemini 3 Pro Simulation) --- ## Executive Summary This document provides a comprehensive implementation plan for the **Translation Dictionary System** - a critical missing component in the DSS Python core that enables mapping between external design token formats and the canonical DSS structure. ### Why This Matters The Translation Dictionary System is the **keystone** of the entire DSS philosophy: - DSS Core is immutable - external systems adapt to DSS, not vice versa - Each client project needs its own mappings from legacy tokens to DSS canonical tokens - Custom props must be isolated in client-specific namespaces - Full traceability from source to DSS to output ### Current State | Component | Status | Location | |-----------|--------|----------| | DSS Core Principles | Documented | `DSS_PRINCIPLES.md` | | Translation Schema | Documented | `DSS_PRINCIPLES.md` | | Python Implementation | **MISSING** | Should be `dss/translations/` | | MCP Integration | **BLOCKED** | Depends on Python implementation | ### Implementation Impact | Phase | Current State | After Implementation | |-------|---------------|----------------------| | Workflow 1 (Figma Import) | Working | Enhanced with translation | | Workflow 2 (Skin Loading) | 60% | 100% - Full skin support | | Workflow 3 (Design Apply) | 10% | 100% - Full token resolution | --- ## 1. Architecture Overview ### 1.1 System Architecture Diagram ``` DSS TRANSLATION DICTIONARY SYSTEM +=======================================================================================+ | | | EXTERNAL SOURCES TRANSLATION LAYER DSS CORE | | ================ ================= ======== | | | | +-------------+ +------------------+ +-----------+ | | | Figma |----+ | Translation | | Canonical | | | | Tokens | | | Dictionary | | Tokens | | | +-------------+ | | Loader | | | | | | +--------+---------+ | color. | | | +-------------+ | Load & | | primary. | | | | Legacy CSS |----+----Parse----> +--------v---------+ Resolve | 500 | | | | Variables | | | Translation |----------> | | | | +-------------+ | | Registry | | spacing. | | | | +--------+---------+ | md | | | +-------------+ | | | | | | | HeroUI/ |----+ +--------v---------+ | etc. | | | | shadcn | | | Token | +-----------+ | | +-------------+ | | Resolver | | | | | +--------+---------+ | | | +-------------+ | | | | | | Custom |----+ +--------v---------+ +-----v-----+ | | | JSON/YAML | | Custom Props | | Merged | | | +-------------+ | Merger | | Theme | | | +--------+---------+ | Output | | | | +-----------+ | | +--------v---------+ | | | | Validation | | | | | Engine | v | | +------------------+ OUTPUT FILES | | - CSS vars | | - SCSS vars | | - JSON tokens | | - TypeScript | | | +=======================================================================================+ ``` ### 1.2 Data Flow Diagram ``` TRANSLATION DICTIONARY DATA FLOW +--------------------------------------------------------------------------------+ | | | PROJECT | | +----------------------------+ | | | .dss/ | | | | config.json |--------> Project Configuration | | | translations/ | | | | figma.json -|--------> Figma Token Mappings | | | legacy-css.json -|--------> Legacy CSS Mappings | | | heroui.json -|--------> HeroUI Mappings | | | shadcn.json -|--------> shadcn Mappings | | | custom.json -|--------> Custom Props Extensions | | +----------------------------+ | | | | | v | | +----------------------------+ | | | TranslationDictionaryLoader| <-- Single entry point | | +----------------------------+ | | | | | v | | +----------------------------+ +------------------------+ | | | TranslationRegistry |<--->| ValidationEngine | | | | (in-memory cache) | | - Schema validation | | | +----------------------------+ | - DSS canonical check | | | | | - Conflict detection | | | | +------------------------+ | | v | | +----------------------------+ | | | TokenResolver | | | | - Resolve source -> DSS | | | | - Resolve DSS -> source | <-- BIDIRECTIONAL | | | - Handle aliases | | | | - Chain references | | | +----------------------------+ | | | | | v | | +----------------------------+ +------------------------+ | | | ThemeMerger |<--->| Base Theme | | | | - Merge base + custom | | (light/dark) | | | | - Apply translations | +------------------------+ | | | - Generate resolved theme | | | +----------------------------+ | | | | | v | | +----------------------------+ | | | ResolvedProjectTheme | <-- Final output | | | - All tokens resolved | | | | - Custom props merged | | | | - Ready for export | | | +----------------------------+ | | | +--------------------------------------------------------------------------------+ ``` ### 1.3 Module Integration Diagram ``` DSS MODULE INTEGRATION +-------------------------------------------------------------+ | | | dss/__init__.py | | +-----------------------------------------------------------+ | | | | | EXISTING MODULES NEW MODULE | | | ================= ========== | | | | | | +-----------+ +------------------+ | | | | ingest |<--------------->| translations | | | | | - CSS | Token | - loader | | | | | - SCSS | extraction | - registry | | | | | - JSON | results | - resolver | | | | | - merge | | - merger | | | | +-----------+ | - validator | | | | ^ | - models | | | | | +------------------+ | | | | ^ | | | +-----------+ | | | | | themes |<-----------------------+ | | | | - default | Base theme | | | | | - light | for merging | | | | | - dark | | | | | +-----------+ | | | | ^ | | | | | | | | | +-----------+ | | | | | models |<-----------------------+ | | | | - Theme | Data structures | | | | | - Token | & types | | | | +-----------+ | | | | ^ | | | | | | | | | +-----------+ | | | | | validators|<-----------------------+ | | | | - schema | Validation | | | | | - project | utilities | | | | +-----------+ | | | | ^ | | | | | v | | | +-----------+ +------------------+ | | | | storybook |<--------------->| MCP Plugin | | | | | - scanner | Theme | (tools/dss_mcp) | | | | | - theme | generation | | | | | +-----------+ +------------------+ | | | | | +-----------------------------------------------------------+ | | +-------------------------------------------------------------+ ``` --- ## 2. File Structure ### 2.1 Complete Module Structure ``` dss-mvp1/dss/translations/ | +-- __init__.py # Module exports +-- models.py # Pydantic data models +-- loader.py # Dictionary file loading +-- registry.py # In-memory translation registry +-- resolver.py # Token path resolution +-- merger.py # Theme + custom props merging +-- validator.py # Schema & semantic validation +-- writer.py # Dictionary file writing +-- utils.py # Utility functions +-- canonical.py # DSS canonical structure definitions | +-- schemas/ # JSON Schema files | +-- translation-v1.schema.json | +-- config.schema.json | +-- presets/ # Pre-built translation dictionaries +-- heroui.json # HeroUI -> DSS mappings +-- shadcn.json # shadcn -> DSS mappings +-- tailwind.json # Tailwind -> DSS mappings ``` ### 2.2 Project `.dss` Structure ``` project-root/ | +-- .dss/ # DSS project configuration | +-- config.json # Project configuration | +-- translations/ # Translation dictionaries | +-- figma.json # Figma source mappings | +-- legacy-css.json # Legacy CSS mappings | +-- custom.json # Custom props for this project | +-- cache/ # Computed/resolved cache +-- resolved-theme.json +-- token-map.json ``` --- ## 3. Data Models ### 3.1 `models.py` - Core Data Models ```python """ Translation Dictionary Data Models Pydantic models for translation dictionary system. """ from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional, Union from uuid import uuid4 from pydantic import BaseModel, Field, ConfigDict, field_validator class TranslationSource(str, Enum): """Source types for translation dictionaries.""" FIGMA = "figma" CSS = "css" SCSS = "scss" HEROUI = "heroui" SHADCN = "shadcn" TAILWIND = "tailwind" JSON = "json" CUSTOM = "custom" class MappingType(str, Enum): """Types of mappings in a translation dictionary.""" TOKEN = "token" COMPONENT = "component" PATTERN = "pattern" class TokenMapping(BaseModel): """Single token mapping from source to DSS canonical.""" model_config = ConfigDict(extra="forbid") source_token: str = Field( ..., description="Source token name (e.g., '--brand-blue', '$primary-color')" ) dss_token: str = Field( ..., description="DSS canonical token path (e.g., 'color.primary.500')" ) source_value: Optional[str] = Field( None, description="Original value from source (for reference)" ) notes: Optional[str] = Field( None, description="Human-readable notes about this mapping" ) confidence: float = Field( default=1.0, ge=0.0, le=1.0, description="Confidence score for auto-generated mappings" ) auto_generated: bool = Field( default=False, description="Whether this mapping was auto-generated" ) class ComponentMapping(BaseModel): """Single component mapping from source to DSS canonical.""" model_config = ConfigDict(extra="forbid") source_component: str = Field( ..., description="Source component (e.g., '.btn-primary', 'HeroButton')" ) dss_component: str = Field( ..., description="DSS canonical component (e.g., 'Button[variant=primary]')" ) prop_mappings: Dict[str, str] = Field( default_factory=dict, description="Prop name mappings (source -> DSS)" ) notes: Optional[str] = Field(None) class PatternMapping(BaseModel): """Pattern mapping for structural translations.""" model_config = ConfigDict(extra="forbid") source_pattern: str = Field( ..., description="Source pattern (e.g., 'form-row', 'card-grid')" ) dss_pattern: str = Field( ..., description="DSS canonical pattern" ) notes: Optional[str] = Field(None) class CustomProp(BaseModel): """Custom property not in DSS core.""" model_config = ConfigDict(extra="forbid") name: str = Field( ..., description="Token name in DSS namespace (e.g., 'color.brand.acme.primary')" ) value: Any = Field( ..., description="Token value" ) type: str = Field( default="string", description="Value type (color, dimension, string, etc.)" ) description: Optional[str] = Field(None) deprecated: bool = Field(default=False) deprecated_message: Optional[str] = Field(None) class TranslationMappings(BaseModel): """Container for all mapping types.""" model_config = ConfigDict(extra="forbid") tokens: Dict[str, str] = Field( default_factory=dict, description="Token mappings: source_token -> dss_token" ) components: Dict[str, str] = Field( default_factory=dict, description="Component mappings: source_component -> dss_component" ) patterns: Dict[str, str] = Field( default_factory=dict, description="Pattern mappings: source_pattern -> dss_pattern" ) class TranslationDictionary(BaseModel): """Complete translation dictionary for a project.""" model_config = ConfigDict(extra="forbid") # Metadata schema_version: str = Field( default="dss-translation-v1", alias="$schema", description="Schema version identifier" ) uuid: str = Field( default_factory=lambda: str(uuid4()), description="Unique identifier for this dictionary" ) project: str = Field( ..., description="Project identifier" ) source: TranslationSource = Field( ..., description="Source type for this dictionary" ) version: str = Field( default="1.0.0", description="Dictionary version" ) created_at: datetime = Field( default_factory=datetime.utcnow ) updated_at: datetime = Field( default_factory=datetime.utcnow ) # Mappings mappings: TranslationMappings = Field( default_factory=TranslationMappings, description="All mappings from source to DSS" ) # Custom extensions custom_props: Dict[str, Any] = Field( default_factory=dict, description="Custom props not in DSS core (namespaced)" ) # Tracking unmapped: List[str] = Field( default_factory=list, description="Source tokens that couldn't be mapped" ) notes: List[str] = Field( default_factory=list, description="Human-readable notes" ) @field_validator('custom_props') @classmethod def validate_custom_props_namespace(cls, v: Dict[str, Any]) -> Dict[str, Any]: """Ensure custom props use proper namespacing.""" for key in v.keys(): # Custom props should be namespaced (e.g., color.brand.acme.primary) if not '.' in key: raise ValueError( f"Custom prop '{key}' must use dot-notation namespace " "(e.g., 'color.brand.project.name')" ) return v class TranslationRegistry(BaseModel): """In-memory registry of all loaded translation dictionaries.""" model_config = ConfigDict(arbitrary_types_allowed=True) dictionaries: Dict[str, TranslationDictionary] = Field( default_factory=dict, description="Loaded dictionaries by source type" ) combined_token_map: Dict[str, str] = Field( default_factory=dict, description="Combined source->DSS token mappings" ) combined_component_map: Dict[str, str] = Field( default_factory=dict, description="Combined source->DSS component mappings" ) all_custom_props: Dict[str, Any] = Field( default_factory=dict, description="Merged custom props from all dictionaries" ) conflicts: List[Dict[str, Any]] = Field( default_factory=list, description="Detected mapping conflicts" ) class ResolvedToken(BaseModel): """A fully resolved token with provenance.""" model_config = ConfigDict(extra="forbid") dss_path: str = Field( ..., description="DSS canonical path (e.g., 'color.primary.500')" ) value: Any = Field( ..., description="Resolved value" ) source_token: Optional[str] = Field( None, description="Original source token if translated" ) source_type: Optional[TranslationSource] = Field( None, description="Source type if translated" ) is_custom: bool = Field( default=False, description="Whether this is a custom prop" ) provenance: List[str] = Field( default_factory=list, description="Resolution chain for debugging" ) class ResolvedTheme(BaseModel): """Fully resolved theme with all translations applied.""" model_config = ConfigDict(arbitrary_types_allowed=True) name: str version: str = "1.0.0" base_theme: str = Field( ..., description="Base theme name (light/dark)" ) tokens: Dict[str, ResolvedToken] = Field( default_factory=dict ) custom_props: Dict[str, ResolvedToken] = Field( default_factory=dict ) translations_applied: List[str] = Field( default_factory=list, description="List of translation dictionaries applied" ) resolved_at: datetime = Field( default_factory=datetime.utcnow ) ``` ### 3.2 JSON Schema - `translation-v1.schema.json` ```json { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://dss.dev/schemas/translation-v1.schema.json", "title": "DSS Translation Dictionary", "description": "Schema for DSS translation dictionary files", "type": "object", "required": ["$schema", "project", "source"], "properties": { "$schema": { "type": "string", "const": "dss-translation-v1", "description": "Schema version identifier" }, "project": { "type": "string", "minLength": 1, "description": "Project identifier" }, "source": { "type": "string", "enum": ["figma", "css", "scss", "heroui", "shadcn", "tailwind", "json", "custom"], "description": "Source type for this dictionary" }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "default": "1.0.0", "description": "Semantic version" }, "mappings": { "type": "object", "properties": { "tokens": { "type": "object", "additionalProperties": { "type": "string", "pattern": "^[a-z][a-z0-9]*\\.[a-z][a-z0-9]*(\\.[a-z0-9]+)*$" }, "description": "Token mappings: source -> DSS canonical path" }, "components": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Component mappings" }, "patterns": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Pattern mappings" } }, "additionalProperties": false }, "custom_props": { "type": "object", "propertyNames": { "pattern": "^[a-z][a-z0-9]*\\.[a-z][a-z0-9]*(\\.[a-z0-9]+)*$" }, "description": "Custom properties in DSS namespace" }, "unmapped": { "type": "array", "items": { "type": "string" }, "description": "Source tokens that couldn't be mapped" }, "notes": { "type": "array", "items": { "type": "string" }, "description": "Human-readable notes" } }, "additionalProperties": false } ``` --- ## 4. Core Classes & Methods ### 4.1 `loader.py` - Dictionary Loader ```python """ Translation Dictionary Loader Loads and parses translation dictionaries from project .dss directory. """ import json from pathlib import Path from typing import Dict, List, Optional, Union from .models import TranslationDictionary, TranslationSource, TranslationRegistry from .validator import TranslationValidator class TranslationDictionaryLoader: """ Loads translation dictionaries from project .dss/translations/ directory. Usage: loader = TranslationDictionaryLoader("/path/to/project") registry = await loader.load_all() # Or load specific dictionary figma_dict = await loader.load_dictionary("figma") """ DEFAULT_DIR = ".dss/translations" def __init__( self, project_path: Union[str, Path], translations_dir: Optional[str] = None, validate: bool = True ): """ Initialize loader. Args: project_path: Root path to project translations_dir: Custom translations directory (default: .dss/translations) validate: Whether to validate dictionaries on load """ self.project_path = Path(project_path).resolve() self.translations_dir = self.project_path / ( translations_dir or self.DEFAULT_DIR ) self.validate = validate self.validator = TranslationValidator() if validate else None async def load_all(self) -> TranslationRegistry: """ Load all translation dictionaries from project. Returns: TranslationRegistry with all loaded dictionaries """ registry = TranslationRegistry() if not self.translations_dir.exists(): return registry for json_file in self.translations_dir.glob("*.json"): try: dictionary = await self.load_dictionary_file(json_file) if dictionary: registry.dictionaries[dictionary.source.value] = dictionary self._merge_to_registry(registry, dictionary) except Exception as e: # Log error but continue loading other dictionaries registry.conflicts.append({ "file": str(json_file), "error": str(e), "type": "load_error" }) return registry async def load_dictionary( self, source: Union[str, TranslationSource] ) -> Optional[TranslationDictionary]: """ Load a specific translation dictionary by source type. Args: source: Source type (e.g., "figma", "css", TranslationSource.FIGMA) Returns: TranslationDictionary or None if not found """ if isinstance(source, str): source = TranslationSource(source) file_path = self.translations_dir / f"{source.value}.json" if not file_path.exists(): return None return await self.load_dictionary_file(file_path) async def load_dictionary_file( self, file_path: Union[str, Path] ) -> Optional[TranslationDictionary]: """ Load a translation dictionary from a specific file. Args: file_path: Path to JSON file Returns: TranslationDictionary or None if invalid """ file_path = Path(file_path) if not file_path.exists(): raise FileNotFoundError(f"Dictionary file not found: {file_path}") with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) # Validate if enabled if self.validator: validation_result = self.validator.validate_dictionary(data) if not validation_result.is_valid: raise ValueError( f"Invalid dictionary {file_path}: " f"{[str(e) for e in validation_result.errors]}" ) return TranslationDictionary(**data) def _merge_to_registry( self, registry: TranslationRegistry, dictionary: TranslationDictionary ) -> None: """Merge dictionary mappings into registry.""" # Merge token mappings for source_token, dss_token in dictionary.mappings.tokens.items(): if source_token in registry.combined_token_map: existing = registry.combined_token_map[source_token] if existing != dss_token: registry.conflicts.append({ "type": "token_conflict", "source_token": source_token, "existing_mapping": existing, "new_mapping": dss_token, "source": dictionary.source.value }) continue registry.combined_token_map[source_token] = dss_token # Merge component mappings for source_comp, dss_comp in dictionary.mappings.components.items(): if source_comp in registry.combined_component_map: existing = registry.combined_component_map[source_comp] if existing != dss_comp: registry.conflicts.append({ "type": "component_conflict", "source_component": source_comp, "existing_mapping": existing, "new_mapping": dss_comp, "source": dictionary.source.value }) continue registry.combined_component_map[source_comp] = dss_comp # Merge custom props for prop_name, prop_value in dictionary.custom_props.items(): if prop_name in registry.all_custom_props: existing = registry.all_custom_props[prop_name] if existing != prop_value: registry.conflicts.append({ "type": "custom_prop_conflict", "prop_name": prop_name, "existing_value": existing, "new_value": prop_value, "source": dictionary.source.value }) continue registry.all_custom_props[prop_name] = prop_value def get_translations_dir(self) -> Path: """Get the translations directory path.""" return self.translations_dir def has_translations(self) -> bool: """Check if project has any translation dictionaries.""" if not self.translations_dir.exists(): return False return any(self.translations_dir.glob("*.json")) def list_available_dictionaries(self) -> List[str]: """List available dictionary source types.""" if not self.translations_dir.exists(): return [] return [ f.stem for f in self.translations_dir.glob("*.json") ] ``` ### 4.2 `resolver.py` - Token Resolution ```python """ Token Resolver Resolves tokens between source formats and DSS canonical structure. Supports bidirectional translation. """ from typing import Any, Dict, List, Optional, Tuple, Union from .models import ( TranslationRegistry, TranslationSource, ResolvedToken, ) from .canonical import DSS_CANONICAL_TOKENS class TokenResolver: """ Resolves tokens between source and DSS canonical formats. Supports: - Source -> DSS translation (forward) - DSS -> Source translation (reverse) - Token path resolution with aliasing - Reference chain resolution Usage: resolver = TokenResolver(registry) # Forward translation dss_token = resolver.resolve_to_dss("--brand-blue") # -> "color.primary.500" # Reverse translation source_token = resolver.resolve_to_source("color.primary.500", "css") # -> "--brand-blue" """ def __init__(self, registry: TranslationRegistry): """ Initialize resolver with translation registry. Args: registry: Loaded TranslationRegistry with mappings """ self.registry = registry self._reverse_map: Dict[str, Dict[str, str]] = {} self._build_reverse_maps() def _build_reverse_maps(self) -> None: """Build reverse lookup maps (DSS -> source) for each source type.""" for source_type, dictionary in self.registry.dictionaries.items(): self._reverse_map[source_type] = { dss: source for source, dss in dictionary.mappings.tokens.items() } def resolve_to_dss( self, source_token: str, source_type: Optional[Union[str, TranslationSource]] = None ) -> Optional[str]: """ Resolve source token to DSS canonical path. Args: source_token: Source token (e.g., "--brand-blue", "$primary") source_type: Optional source type hint (searches all if not provided) Returns: DSS canonical path or None if not found """ # Direct lookup in combined map if source_token in self.registry.combined_token_map: return self.registry.combined_token_map[source_token] # If source type specified, look only there if source_type: if isinstance(source_type, str): source_type = TranslationSource(source_type) dictionary = self.registry.dictionaries.get(source_type.value) if dictionary: return dictionary.mappings.tokens.get(source_token) # Try normalization patterns normalized = self._normalize_token_name(source_token) return self.registry.combined_token_map.get(normalized) def resolve_to_source( self, dss_token: str, source_type: Union[str, TranslationSource] ) -> Optional[str]: """ Resolve DSS token to source format (reverse translation). Args: dss_token: DSS canonical path (e.g., "color.primary.500") source_type: Target source type Returns: Source token name or None if not mapped """ if isinstance(source_type, str): source_type_str = source_type else: source_type_str = source_type.value reverse_map = self._reverse_map.get(source_type_str, {}) return reverse_map.get(dss_token) def resolve_token_value( self, source_token: str, base_theme_tokens: Dict[str, Any], source_type: Optional[Union[str, TranslationSource]] = None ) -> Optional[ResolvedToken]: """ Fully resolve a source token to its DSS value. Args: source_token: Source token name base_theme_tokens: Base theme token values source_type: Optional source type hint Returns: ResolvedToken with full provenance or None """ # Get DSS path dss_path = self.resolve_to_dss(source_token, source_type) if not dss_path: # Check if it's a custom prop if source_token in self.registry.all_custom_props: return ResolvedToken( dss_path=source_token, value=self.registry.all_custom_props[source_token], source_token=source_token, is_custom=True, provenance=[f"custom_prop: {source_token}"] ) return None # Resolve value from base theme value = self._get_token_value(dss_path, base_theme_tokens) # Determine source type if not provided resolved_source = source_type if resolved_source is None: for src_type, dictionary in self.registry.dictionaries.items(): if source_token in dictionary.mappings.tokens: resolved_source = TranslationSource(src_type) break return ResolvedToken( dss_path=dss_path, value=value, source_token=source_token, source_type=resolved_source if isinstance(resolved_source, TranslationSource) else ( TranslationSource(resolved_source) if resolved_source else None ), is_custom=False, provenance=[ f"source: {source_token}", f"mapped_to: {dss_path}", f"value: {value}" ] ) def resolve_all_mappings( self, base_theme_tokens: Dict[str, Any] ) -> Dict[str, ResolvedToken]: """ Resolve all mapped tokens to their DSS values. Args: base_theme_tokens: Base theme token values Returns: Dict of DSS path -> ResolvedToken """ resolved = {} # Resolve all mapped tokens for source_token, dss_path in self.registry.combined_token_map.items(): value = self._get_token_value(dss_path, base_theme_tokens) # Find source type source_type = None for src_type, dictionary in self.registry.dictionaries.items(): if source_token in dictionary.mappings.tokens: source_type = TranslationSource(src_type) break resolved[dss_path] = ResolvedToken( dss_path=dss_path, value=value, source_token=source_token, source_type=source_type, is_custom=False, provenance=[f"source: {source_token}", f"mapped_to: {dss_path}"] ) # Add custom props for prop_name, prop_value in self.registry.all_custom_props.items(): resolved[prop_name] = ResolvedToken( dss_path=prop_name, value=prop_value, is_custom=True, provenance=[f"custom_prop: {prop_name}"] ) return resolved def _get_token_value( self, dss_path: str, base_tokens: Dict[str, Any] ) -> Any: """Get token value from base theme using DSS path.""" # Handle nested paths (e.g., "color.primary.500") parts = dss_path.split('.') current = base_tokens for part in parts: if isinstance(current, dict): current = current.get(part) if current is None: break else: return None # If we got a DesignToken object, extract value if hasattr(current, 'value'): return current.value return current def _normalize_token_name(self, token: str) -> str: """Normalize token name for lookup.""" # Remove common prefixes normalized = token.lstrip('-$') # Convert various formats to dot notation normalized = normalized.replace('-', '.').replace('_', '.') # Handle var() references if normalized.startswith('var(') and normalized.endswith(')'): normalized = normalized[4:-1].lstrip('-') return normalized.lower() def get_unmapped_tokens(self) -> List[str]: """Get list of tokens that couldn't be mapped.""" unmapped = [] for dictionary in self.registry.dictionaries.values(): unmapped.extend(dictionary.unmapped) return list(set(unmapped)) def validate_dss_path(self, path: str) -> bool: """Validate that a path matches DSS canonical structure.""" return path in DSS_CANONICAL_TOKENS or self._is_valid_custom_namespace(path) def _is_valid_custom_namespace(self, path: str) -> bool: """Check if path uses valid custom namespace.""" parts = path.split('.') if len(parts) < 3: return False # Custom props should be like: color.brand.acme.primary return parts[1] == 'brand' or parts[1] == 'custom' ``` ### 4.3 `merger.py` - Theme Merger ```python """ Theme Merger Merges base DSS theme with translation mappings and custom props. """ from typing import Any, Dict, List, Optional, Union from datetime import datetime from .models import ( TranslationRegistry, ResolvedToken, ResolvedTheme, ) from .resolver import TokenResolver from dss.models.theme import Theme, DesignToken, TokenCategory from dss.themes.default_themes import get_default_light_theme, get_default_dark_theme class ThemeMerger: """ Merges base DSS theme with project-specific customizations. The merge hierarchy: 1. Base Theme (DSS Light or Dark) 2. Translation Mappings (external tokens -> DSS) 3. Custom Props (project-specific extensions) Usage: merger = ThemeMerger(registry) resolved = await merger.merge(base_theme="light") """ def __init__(self, registry: TranslationRegistry): """ Initialize merger with translation registry. Args: registry: TranslationRegistry with loaded dictionaries """ self.registry = registry self.resolver = TokenResolver(registry) async def merge( self, base_theme: str = "light", project_name: Optional[str] = None ) -> ResolvedTheme: """ Merge base theme with translations and custom props. Args: base_theme: Base theme name ("light" or "dark") project_name: Project name for resolved theme Returns: ResolvedTheme with all tokens resolved """ # Get base theme if base_theme == "light": theme = get_default_light_theme() elif base_theme == "dark": theme = get_default_dark_theme() else: raise ValueError(f"Unknown base theme: {base_theme}") # Convert theme tokens to dict for resolution base_tokens = self._theme_to_dict(theme) # Resolve all mapped tokens resolved_tokens = self.resolver.resolve_all_mappings(base_tokens) # Separate core tokens from custom props core_tokens = {} custom_props = {} for dss_path, resolved in resolved_tokens.items(): if resolved.is_custom: custom_props[dss_path] = resolved else: core_tokens[dss_path] = resolved # Add base theme tokens that aren't in mappings for token_name, token in theme.tokens.items(): # Normalize token name to DSS path dss_path = self._normalize_to_dss_path(token_name) if dss_path not in core_tokens: core_tokens[dss_path] = ResolvedToken( dss_path=dss_path, value=token.value, is_custom=False, provenance=[f"base_theme: {base_theme}"] ) return ResolvedTheme( name=project_name or f"resolved-{base_theme}", version="1.0.0", base_theme=base_theme, tokens=core_tokens, custom_props=custom_props, translations_applied=[ dict_name for dict_name in self.registry.dictionaries.keys() ], resolved_at=datetime.utcnow() ) def _theme_to_dict(self, theme: Theme) -> Dict[str, Any]: """Convert Theme object to nested dict for resolution.""" result = {} for token_name, token in theme.tokens.items(): # Convert flat token names to nested structure parts = self._normalize_to_dss_path(token_name).split('.') current = result for part in parts[:-1]: if part not in current: current[part] = {} current = current[part] current[parts[-1]] = token.value return result def _normalize_to_dss_path(self, token_name: str) -> str: """Normalize token name to DSS canonical path.""" # Handle various formats normalized = token_name.replace('-', '.').replace('_', '.') # Map common prefixes prefix_map = { 'space.': 'spacing.', 'radius.': 'border.radius.', 'text.': 'typography.size.', } for old, new in prefix_map.items(): if normalized.startswith(old): normalized = new + normalized[len(old):] break return normalized async def merge_custom_props( self, resolved_theme: ResolvedTheme, additional_props: Dict[str, Any] ) -> ResolvedTheme: """ Add additional custom props to a resolved theme. Args: resolved_theme: Existing resolved theme additional_props: Additional custom props to merge Returns: Updated ResolvedTheme """ for prop_name, prop_value in additional_props.items(): resolved_theme.custom_props[prop_name] = ResolvedToken( dss_path=prop_name, value=prop_value, is_custom=True, provenance=["additional_custom_prop"] ) resolved_theme.resolved_at = datetime.utcnow() return resolved_theme def export_as_theme(self, resolved: ResolvedTheme) -> Theme: """ Convert ResolvedTheme back to Theme model. Args: resolved: ResolvedTheme to convert Returns: Theme model instance """ tokens = {} # Add core tokens for dss_path, resolved_token in resolved.tokens.items(): token_name = dss_path.replace('.', '-') tokens[token_name] = DesignToken( name=token_name, value=resolved_token.value, type=self._infer_type(dss_path, resolved_token.value), category=self._infer_category(dss_path), source=f"resolved:{resolved.base_theme}" ) # Add custom props for dss_path, resolved_token in resolved.custom_props.items(): token_name = dss_path.replace('.', '-') tokens[token_name] = DesignToken( name=token_name, value=resolved_token.value, type=self._infer_type(dss_path, resolved_token.value), category=TokenCategory.OTHER, source="custom_prop" ) return Theme( name=resolved.name, version=resolved.version, tokens=tokens ) def _infer_type(self, path: str, value: Any) -> str: """Infer token type from path and value.""" if 'color' in path: return 'color' if 'spacing' in path or 'size' in path or 'radius' in path: return 'dimension' if 'font' in path: return 'typography' if 'shadow' in path: return 'shadow' return 'string' def _infer_category(self, path: str) -> TokenCategory: """Infer token category from DSS path.""" if path.startswith('color'): return TokenCategory.COLOR if path.startswith('spacing'): return TokenCategory.SPACING if path.startswith('typography') or path.startswith('font'): return TokenCategory.TYPOGRAPHY if path.startswith('border') or path.startswith('radius'): return TokenCategory.RADIUS if path.startswith('shadow'): return TokenCategory.SHADOW return TokenCategory.OTHER ``` ### 4.4 `validator.py` - Validation Engine ```python """ Translation Dictionary Validator Validates translation dictionary schema and semantic correctness. """ import json import re from pathlib import Path from typing import Any, Dict, List, Optional from pydantic import ValidationError as PydanticValidationError from .models import TranslationDictionary, TranslationSource from .canonical import DSS_CANONICAL_TOKENS, DSS_CANONICAL_COMPONENTS class ValidationError: """Single validation error.""" def __init__( self, message: str, path: Optional[str] = None, severity: str = "error" ): self.message = message self.path = path self.severity = severity # error, warning, info def __str__(self) -> str: if self.path: return f"[{self.severity}] {self.path}: {self.message}" return f"[{self.severity}] {self.message}" class ValidationResult: """Validation result container.""" def __init__(self): self.is_valid = True self.errors: List[ValidationError] = [] self.warnings: List[ValidationError] = [] self.info: List[ValidationError] = [] def add_error(self, message: str, path: Optional[str] = None) -> None: self.errors.append(ValidationError(message, path, "error")) self.is_valid = False def add_warning(self, message: str, path: Optional[str] = None) -> None: self.warnings.append(ValidationError(message, path, "warning")) def add_info(self, message: str, path: Optional[str] = None) -> None: self.info.append(ValidationError(message, path, "info")) class TranslationValidator: """ Validates translation dictionaries. Validation stages: 1. Schema validation - JSON structure matches Pydantic model 2. Token path validation - DSS paths are canonical 3. Component validation - Component mappings are valid 4. Custom prop validation - Namespacing is correct 5. Consistency validation - No conflicts or duplicates """ # Valid DSS path pattern DSS_PATH_PATTERN = re.compile( r'^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$' ) def __init__( self, strict: bool = False, allow_unknown_tokens: bool = True ): """ Initialize validator. Args: strict: If True, unknown DSS tokens are errors (not warnings) allow_unknown_tokens: If False, all tokens must exist in canonical """ self.strict = strict self.allow_unknown_tokens = allow_unknown_tokens def validate_dictionary( self, data: Dict[str, Any] ) -> ValidationResult: """ Validate a translation dictionary. Args: data: Dictionary data to validate Returns: ValidationResult with all errors/warnings """ result = ValidationResult() # Stage 1: Schema validation self._validate_schema(data, result) if not result.is_valid: return result # Stage 2: Token path validation self._validate_token_paths(data, result) # Stage 3: Component validation self._validate_components(data, result) # Stage 4: Custom prop validation self._validate_custom_props(data, result) # Stage 5: Consistency validation self._validate_consistency(data, result) return result def _validate_schema( self, data: Dict[str, Any], result: ValidationResult ) -> None: """Stage 1: Validate JSON structure.""" try: TranslationDictionary(**data) except PydanticValidationError as e: for error in e.errors(): path = ".".join(str(loc) for loc in error["loc"]) result.add_error(error["msg"], path) except Exception as e: result.add_error(f"Schema validation failed: {str(e)}") def _validate_token_paths( self, data: Dict[str, Any], result: ValidationResult ) -> None: """Stage 2: Validate DSS token paths.""" mappings = data.get("mappings", {}) tokens = mappings.get("tokens", {}) for source_token, dss_path in tokens.items(): # Validate path format if not self.DSS_PATH_PATTERN.match(dss_path): result.add_error( f"Invalid DSS path format: '{dss_path}' " "(must be dot-notation like 'color.primary.500')", f"mappings.tokens.{source_token}" ) continue # Validate against canonical structure if dss_path not in DSS_CANONICAL_TOKENS: if self._is_custom_namespace(dss_path): # Custom namespaces are allowed result.add_info( f"Custom namespace token: {dss_path}", f"mappings.tokens.{source_token}" ) elif self.allow_unknown_tokens: result.add_warning( f"DSS token not in canonical structure: {dss_path}", f"mappings.tokens.{source_token}" ) else: result.add_error( f"Unknown DSS token: {dss_path}", f"mappings.tokens.{source_token}" ) def _validate_components( self, data: Dict[str, Any], result: ValidationResult ) -> None: """Stage 3: Validate component mappings.""" mappings = data.get("mappings", {}) components = mappings.get("components", {}) for source_comp, dss_comp in components.items(): # Extract base component name (before any variant specifiers) base_comp = dss_comp.split('[')[0] if base_comp not in DSS_CANONICAL_COMPONENTS: result.add_warning( f"Component not in DSS canonical set: {base_comp}", f"mappings.components.{source_comp}" ) # Validate variant syntax if present if '[' in dss_comp: if not self._validate_variant_syntax(dss_comp): result.add_error( f"Invalid variant syntax: {dss_comp}", f"mappings.components.{source_comp}" ) def _validate_custom_props( self, data: Dict[str, Any], result: ValidationResult ) -> None: """Stage 4: Validate custom prop namespacing.""" custom_props = data.get("custom_props", {}) for prop_name, prop_value in custom_props.items(): # Must use dot notation if '.' not in prop_name: result.add_error( f"Custom prop must use dot-notation namespace: {prop_name}", f"custom_props.{prop_name}" ) continue # Should use brand/custom namespace parts = prop_name.split('.') if len(parts) >= 2 and parts[1] not in ('brand', 'custom'): result.add_warning( f"Custom prop should use 'brand' or 'custom' namespace: {prop_name}. " f"Recommended: {parts[0]}.brand.{'.'.join(parts[1:])}", f"custom_props.{prop_name}" ) def _validate_consistency( self, data: Dict[str, Any], result: ValidationResult ) -> None: """Stage 5: Validate internal consistency.""" mappings = data.get("mappings", {}) tokens = mappings.get("tokens", {}) custom_props = data.get("custom_props", {}) # Check for duplicate DSS targets dss_targets = list(tokens.values()) seen = set() for target in dss_targets: if target in seen: result.add_warning( f"Multiple source tokens map to same DSS token: {target}", "mappings.tokens" ) seen.add(target) # Check custom props don't conflict with mappings for prop_name in custom_props.keys(): if prop_name in tokens.values(): result.add_error( f"Custom prop conflicts with mapping target: {prop_name}", f"custom_props.{prop_name}" ) def _is_custom_namespace(self, path: str) -> bool: """Check if path uses custom namespace.""" parts = path.split('.') if len(parts) >= 2: return parts[1] in ('brand', 'custom') return False def _validate_variant_syntax(self, comp: str) -> bool: """Validate component variant syntax like Button[variant=primary].""" if '[' not in comp: return True # Check for matching brackets if comp.count('[') != comp.count(']'): return False # Extract variant part variant_match = re.search(r'\[([^\]]+)\]', comp) if not variant_match: return False # Validate key=value format variant_str = variant_match.group(1) for pair in variant_str.split(','): if '=' not in pair: return False key, value = pair.split('=', 1) if not key.strip() or not value.strip(): return False return True def validate_file(self, file_path: str) -> ValidationResult: """ Validate a translation dictionary file. Args: file_path: Path to JSON file Returns: ValidationResult """ result = ValidationResult() try: with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) except json.JSONDecodeError as e: result.add_error(f"Invalid JSON: {str(e)}") return result except FileNotFoundError: result.add_error(f"File not found: {file_path}") return result return self.validate_dictionary(data) ``` ### 4.5 `canonical.py` - DSS Canonical Definitions ```python """ DSS Canonical Structure Definitions Defines the immutable DSS canonical token and component structure. These definitions are used for validation and auto-completion. """ from typing import Set, Dict, List # DSS Canonical Token Paths # These are the core tokens that DSS defines DSS_CANONICAL_TOKENS: Set[str] = { # Colors - Primary "color.primary.50", "color.primary.100", "color.primary.200", "color.primary.300", "color.primary.400", "color.primary.500", "color.primary.600", "color.primary.700", "color.primary.800", "color.primary.900", # Colors - Secondary "color.secondary.50", "color.secondary.100", "color.secondary.200", "color.secondary.300", "color.secondary.400", "color.secondary.500", "color.secondary.600", "color.secondary.700", "color.secondary.800", "color.secondary.900", # Colors - Neutral "color.neutral.50", "color.neutral.100", "color.neutral.200", "color.neutral.300", "color.neutral.400", "color.neutral.500", "color.neutral.600", "color.neutral.700", "color.neutral.800", "color.neutral.900", # Colors - Semantic "color.success.500", "color.warning.500", "color.danger.500", "color.info.500", "color.accent.500", # Colors - Surface "color.background", "color.foreground", "color.muted", "color.border", "color.ring", # Spacing "spacing.xs", "spacing.sm", "spacing.md", "spacing.lg", "spacing.xl", "spacing.2xl", "spacing.base", # Typography - Size "typography.size.xs", "typography.size.sm", "typography.size.base", "typography.size.lg", "typography.size.xl", "typography.size.2xl", "typography.size.3xl", "typography.size.4xl", # Typography - Weight "typography.weight.light", "typography.weight.normal", "typography.weight.medium", "typography.weight.semibold", "typography.weight.bold", # Typography - Line Height "typography.lineHeight.tight", "typography.lineHeight.normal", "typography.lineHeight.relaxed", # Typography - Font Family "typography.family.sans", "typography.family.serif", "typography.family.mono", # Border Radius "border.radius.none", "border.radius.sm", "border.radius.md", "border.radius.lg", "border.radius.xl", "border.radius.full", # Border Width "border.width.none", "border.width.thin", "border.width.default", "border.width.thick", # Shadows "shadow.none", "shadow.sm", "shadow.md", "shadow.lg", "shadow.xl", "shadow.inner", # Motion - Duration "motion.duration.instant", "motion.duration.fast", "motion.duration.normal", "motion.duration.slow", # Motion - Easing "motion.easing.linear", "motion.easing.ease", "motion.easing.easeIn", "motion.easing.easeOut", "motion.easing.easeInOut", # Z-Index "zIndex.base", "zIndex.dropdown", "zIndex.sticky", "zIndex.fixed", "zIndex.modal", "zIndex.popover", "zIndex.tooltip", # Opacity "opacity.0", "opacity.25", "opacity.50", "opacity.75", "opacity.100", # Breakpoints "breakpoint.sm", "breakpoint.md", "breakpoint.lg", "breakpoint.xl", "breakpoint.2xl", } # Commonly used aliases for DSS tokens DSS_TOKEN_ALIASES: Dict[str, str] = { # Color aliases "color.primary": "color.primary.500", "color.secondary": "color.secondary.500", "color.success": "color.success.500", "color.warning": "color.warning.500", "color.danger": "color.danger.500", "color.destructive": "color.danger.500", "color.error": "color.danger.500", # Spacing aliases "space.xs": "spacing.xs", "space.sm": "spacing.sm", "space.md": "spacing.md", "space.lg": "spacing.lg", "space.xl": "spacing.xl", # Radius aliases "radius.sm": "border.radius.sm", "radius.md": "border.radius.md", "radius.lg": "border.radius.lg", # Typography aliases "font.size.base": "typography.size.base", "font.weight.bold": "typography.weight.bold", "lineHeight.normal": "typography.lineHeight.normal", } # DSS Canonical Components DSS_CANONICAL_COMPONENTS: Set[str] = { # Primitives "Button", "Input", "Textarea", "Select", "Checkbox", "Radio", "RadioGroup", "Switch", "Slider", "Toggle", # Layout "Box", "Flex", "Grid", "Container", "Stack", "Spacer", "Divider", # Data Display "Card", "Avatar", "Badge", "Chip", "Tag", "Icon", "Image", "Table", "List", "ListItem", # Feedback "Alert", "Toast", "Progress", "Spinner", "Skeleton", "Tooltip", # Overlay "Modal", "Dialog", "Drawer", "Popover", "Dropdown", "DropdownMenu", "ContextMenu", # Navigation "Tabs", "TabList", "Tab", "TabPanel", "Breadcrumb", "Pagination", "Menu", "MenuItem", "NavLink", "Link", # Typography "Text", "Heading", "Label", "Code", # Forms "Form", "FormControl", "FormLabel", "FormHelperText", "FormErrorMessage", } # DSS Component Variants DSS_COMPONENT_VARIANTS: Dict[str, List[str]] = { "Button": ["variant", "size", "colorScheme", "isDisabled", "isLoading"], "Input": ["variant", "size", "isDisabled", "isInvalid", "isReadOnly"], "Card": ["variant", "size", "shadow"], "Badge": ["variant", "colorScheme", "size"], "Alert": ["status", "variant"], "Modal": ["size", "isCentered", "scrollBehavior"], } # Valid variant values DSS_VARIANT_VALUES: Dict[str, Dict[str, List[str]]] = { "Button": { "variant": ["solid", "outline", "ghost", "link", "unstyled"], "size": ["xs", "sm", "md", "lg"], "colorScheme": ["primary", "secondary", "success", "warning", "danger"], }, "Input": { "variant": ["outline", "filled", "flushed", "unstyled"], "size": ["xs", "sm", "md", "lg"], }, "Card": { "variant": ["elevated", "outline", "filled", "unstyled"], "size": ["sm", "md", "lg"], }, } def get_canonical_token_categories() -> Dict[str, List[str]]: """Get tokens organized by category.""" categories: Dict[str, List[str]] = {} for token in DSS_CANONICAL_TOKENS: parts = token.split('.') category = parts[0] if category not in categories: categories[category] = [] categories[category].append(token) return categories def is_valid_dss_token(path: str) -> bool: """Check if token path is in canonical structure or valid custom namespace.""" if path in DSS_CANONICAL_TOKENS: return True # Check aliases if path in DSS_TOKEN_ALIASES: return True # Check custom namespace parts = path.split('.') if len(parts) >= 3 and parts[1] in ('brand', 'custom'): return True return False def resolve_alias(path: str) -> str: """Resolve token alias to canonical path.""" return DSS_TOKEN_ALIASES.get(path, path) ``` ### 4.6 `writer.py` - Dictionary Writer ```python """ Translation Dictionary Writer Writes and updates translation dictionary files. """ import json from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Union from .models import TranslationDictionary, TranslationSource, TranslationMappings class TranslationDictionaryWriter: """ Writes translation dictionaries to project .dss/translations/ directory. Usage: writer = TranslationDictionaryWriter("/path/to/project") # Create new dictionary await writer.create( source=TranslationSource.CSS, project="my-project", token_mappings={"--brand-blue": "color.primary.500"} ) # Add mapping to existing dictionary await writer.add_mapping( source=TranslationSource.CSS, source_token="--brand-green", dss_token="color.success.500" ) """ DEFAULT_DIR = ".dss/translations" def __init__( self, project_path: Union[str, Path], translations_dir: Optional[str] = None ): """ Initialize writer. Args: project_path: Root path to project translations_dir: Custom translations directory """ self.project_path = Path(project_path).resolve() self.translations_dir = self.project_path / ( translations_dir or self.DEFAULT_DIR ) async def create( self, source: Union[str, TranslationSource], project: str, token_mappings: Optional[Dict[str, str]] = None, component_mappings: Optional[Dict[str, str]] = None, custom_props: Optional[Dict[str, Any]] = None, notes: Optional[List[str]] = None ) -> TranslationDictionary: """ Create a new translation dictionary. Args: source: Source type project: Project identifier token_mappings: Initial token mappings component_mappings: Initial component mappings custom_props: Initial custom props notes: Optional notes Returns: Created TranslationDictionary """ if isinstance(source, str): source = TranslationSource(source) # Ensure directory exists self.translations_dir.mkdir(parents=True, exist_ok=True) # Create dictionary dictionary = TranslationDictionary( project=project, source=source, mappings=TranslationMappings( tokens=token_mappings or {}, components=component_mappings or {}, ), custom_props=custom_props or {}, notes=notes or [] ) # Write to file file_path = self.translations_dir / f"{source.value}.json" await self._write_file(file_path, dictionary) return dictionary async def update( self, source: Union[str, TranslationSource], token_mappings: Optional[Dict[str, str]] = None, component_mappings: Optional[Dict[str, str]] = None, custom_props: Optional[Dict[str, Any]] = None, notes: Optional[List[str]] = None ) -> TranslationDictionary: """ Update an existing translation dictionary. Args: source: Source type token_mappings: Token mappings to add/update component_mappings: Component mappings to add/update custom_props: Custom props to add/update notes: Notes to append Returns: Updated TranslationDictionary """ if isinstance(source, str): source = TranslationSource(source) file_path = self.translations_dir / f"{source.value}.json" if not file_path.exists(): raise FileNotFoundError( f"Dictionary not found: {file_path}. Use create() first." ) # Load existing with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) dictionary = TranslationDictionary(**data) # Update mappings if token_mappings: dictionary.mappings.tokens.update(token_mappings) if component_mappings: dictionary.mappings.components.update(component_mappings) if custom_props: dictionary.custom_props.update(custom_props) if notes: dictionary.notes.extend(notes) dictionary.updated_at = datetime.utcnow() # Write back await self._write_file(file_path, dictionary) return dictionary async def add_mapping( self, source: Union[str, TranslationSource], source_token: str, dss_token: str ) -> None: """ Add a single token mapping to a dictionary. Args: source: Source type source_token: Source token name dss_token: DSS canonical path """ await self.update( source=source, token_mappings={source_token: dss_token} ) async def add_custom_prop( self, source: Union[str, TranslationSource], prop_name: str, prop_value: Any ) -> None: """ Add a custom prop to a dictionary. Args: source: Source type prop_name: Property name (must use DSS namespace) prop_value: Property value """ # Validate namespace if '.' not in prop_name: raise ValueError( f"Custom prop must use dot-notation namespace: {prop_name}" ) await self.update( source=source, custom_props={prop_name: prop_value} ) async def remove_mapping( self, source: Union[str, TranslationSource], source_token: str ) -> None: """ Remove a token mapping from a dictionary. Args: source: Source type source_token: Source token to remove """ if isinstance(source, str): source = TranslationSource(source) file_path = self.translations_dir / f"{source.value}.json" if not file_path.exists(): return with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) dictionary = TranslationDictionary(**data) if source_token in dictionary.mappings.tokens: del dictionary.mappings.tokens[source_token] dictionary.updated_at = datetime.utcnow() await self._write_file(file_path, dictionary) async def mark_unmapped( self, source: Union[str, TranslationSource], unmapped_tokens: List[str] ) -> None: """ Add tokens to unmapped list. Args: source: Source type unmapped_tokens: List of tokens that couldn't be mapped """ if isinstance(source, str): source = TranslationSource(source) file_path = self.translations_dir / f"{source.value}.json" if not file_path.exists(): return with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) dictionary = TranslationDictionary(**data) # Add unique unmapped tokens existing = set(dictionary.unmapped) for token in unmapped_tokens: if token not in existing: dictionary.unmapped.append(token) dictionary.updated_at = datetime.utcnow() await self._write_file(file_path, dictionary) async def _write_file( self, file_path: Path, dictionary: TranslationDictionary ) -> None: """Write dictionary to JSON file.""" data = dictionary.model_dump(by_alias=True, mode='json') # Convert datetime to ISO format data['created_at'] = dictionary.created_at.isoformat() data['updated_at'] = dictionary.updated_at.isoformat() with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) def delete( self, source: Union[str, TranslationSource] ) -> bool: """ Delete a translation dictionary file. Args: source: Source type Returns: True if deleted, False if not found """ if isinstance(source, str): source = TranslationSource(source) file_path = self.translations_dir / f"{source.value}.json" if file_path.exists(): file_path.unlink() return True return False ``` ### 4.7 `__init__.py` - Module Exports ```python """ DSS Translation Dictionary Module Provides translation between external design token formats and DSS canonical structure. """ from .models import ( TranslationSource, MappingType, TokenMapping, ComponentMapping, PatternMapping, CustomProp, TranslationMappings, TranslationDictionary, TranslationRegistry, ResolvedToken, ResolvedTheme, ) from .loader import TranslationDictionaryLoader from .resolver import TokenResolver from .merger import ThemeMerger from .validator import TranslationValidator, ValidationResult, ValidationError from .writer import TranslationDictionaryWriter from .canonical import ( DSS_CANONICAL_TOKENS, DSS_CANONICAL_COMPONENTS, DSS_TOKEN_ALIASES, DSS_COMPONENT_VARIANTS, is_valid_dss_token, resolve_alias, get_canonical_token_categories, ) __all__ = [ # Models "TranslationSource", "MappingType", "TokenMapping", "ComponentMapping", "PatternMapping", "CustomProp", "TranslationMappings", "TranslationDictionary", "TranslationRegistry", "ResolvedToken", "ResolvedTheme", # Loader "TranslationDictionaryLoader", # Resolver "TokenResolver", # Merger "ThemeMerger", # Validator "TranslationValidator", "ValidationResult", "ValidationError", # Writer "TranslationDictionaryWriter", # Canonical Definitions "DSS_CANONICAL_TOKENS", "DSS_CANONICAL_COMPONENTS", "DSS_TOKEN_ALIASES", "DSS_COMPONENT_VARIANTS", "is_valid_dss_token", "resolve_alias", "get_canonical_token_categories", ] ``` --- ## 5. Implementation Phases ### Phase 1: Foundation (Days 1-2) ``` Day 1: +-- models.py # Complete data models +-- canonical.py # DSS canonical definitions +-- __init__.py # Module exports Day 2: +-- loader.py # Basic dictionary loading +-- validator.py # Schema validation only +-- schemas/ +-- translation-v1.schema.json ``` **Deliverables:** - All Pydantic models defined - Basic loading from .dss/translations/ - Schema validation working - Unit tests for models ### Phase 2: Core Functionality (Days 3-4) ``` Day 3: +-- resolver.py # Token resolution +-- utils.py # Utility functions Day 4: +-- merger.py # Theme merging +-- writer.py # Dictionary writing ``` **Deliverables:** - Bidirectional token resolution - Theme + custom props merging - Create/update dictionary files - Integration tests ### Phase 3: Presets & Polish (Day 5) ``` Day 5: +-- presets/ +-- heroui.json # HeroUI translations +-- shadcn.json # shadcn translations +-- tailwind.json # Tailwind translations +-- Enhanced validation +-- Full test coverage ``` **Deliverables:** - Pre-built translation dictionaries - Semantic validation (not just schema) - 90%+ test coverage - Documentation --- ## 6. Testing Strategy ### 6.1 Test Structure ``` tests/unit/translations/ | +-- test_models.py # Model validation +-- test_loader.py # Dictionary loading +-- test_resolver.py # Token resolution +-- test_merger.py # Theme merging +-- test_validator.py # Validation logic +-- test_writer.py # File writing +-- test_canonical.py # Canonical definitions tests/integration/translations/ | +-- test_full_workflow.py # End-to-end workflows +-- test_mcp_integration.py # MCP tool integration tests/fixtures/translations/ | +-- valid_dictionary.json # Valid test dictionaries +-- invalid_schema.json # Invalid schema tests +-- conflict_test.json # Conflict detection tests ``` ### 6.2 Key Test Cases ```python # tests/unit/translations/test_resolver.py import pytest from dss.translations import ( TranslationRegistry, TranslationDictionary, TranslationSource, TokenResolver, ) class TestTokenResolver: """Test suite for TokenResolver.""" @pytest.fixture def sample_registry(self): """Create registry with sample mappings.""" dictionary = TranslationDictionary( project="test", source=TranslationSource.CSS, mappings={ "tokens": { "--brand-blue": "color.primary.500", "--brand-dark": "color.primary.700", "--text-main": "color.neutral.900", } } ) registry = TranslationRegistry() registry.dictionaries["css"] = dictionary registry.combined_token_map = dictionary.mappings.tokens return registry def test_forward_resolution(self, sample_registry): """Test source -> DSS resolution.""" resolver = TokenResolver(sample_registry) result = resolver.resolve_to_dss("--brand-blue") assert result == "color.primary.500" def test_reverse_resolution(self, sample_registry): """Test DSS -> source resolution.""" resolver = TokenResolver(sample_registry) result = resolver.resolve_to_source("color.primary.500", "css") assert result == "--brand-blue" def test_unknown_token_returns_none(self, sample_registry): """Test that unknown tokens return None.""" resolver = TokenResolver(sample_registry) result = resolver.resolve_to_dss("--unknown-token") assert result is None def test_normalize_var_reference(self, sample_registry): """Test normalization of var() references.""" resolver = TokenResolver(sample_registry) # Should normalize var(--brand-blue) to --brand-blue result = resolver.resolve_to_dss("var(--brand-blue)") assert result == "color.primary.500" ``` ### 6.3 Integration Test Example ```python # tests/integration/translations/test_full_workflow.py import pytest import tempfile from pathlib import Path from dss.translations import ( TranslationDictionaryLoader, TranslationDictionaryWriter, ThemeMerger, TokenResolver, TranslationSource, ) class TestFullWorkflow: """Test complete translation workflow.""" @pytest.fixture def temp_project(self): """Create temporary project directory.""" with tempfile.TemporaryDirectory() as tmpdir: project_path = Path(tmpdir) translations_dir = project_path / ".dss" / "translations" translations_dir.mkdir(parents=True) yield project_path @pytest.mark.asyncio async def test_create_load_resolve_workflow(self, temp_project): """Test: Create dictionary -> Load -> Resolve -> Merge.""" # 1. Create dictionary writer = TranslationDictionaryWriter(temp_project) await writer.create( source=TranslationSource.CSS, project="test-project", token_mappings={ "--brand-blue": "color.primary.500", "--space-sm": "spacing.sm", }, custom_props={ "color.brand.test.accent": "#FF5733" } ) # 2. Load dictionary loader = TranslationDictionaryLoader(temp_project) registry = await loader.load_all() assert len(registry.dictionaries) == 1 assert "css" in registry.dictionaries # 3. Resolve tokens resolver = TokenResolver(registry) dss_path = resolver.resolve_to_dss("--brand-blue") assert dss_path == "color.primary.500" source_token = resolver.resolve_to_source("color.primary.500", "css") assert source_token == "--brand-blue" # 4. Merge with base theme merger = ThemeMerger(registry) resolved_theme = await merger.merge(base_theme="light", project_name="test") assert resolved_theme.name == "test" assert "color.brand.test.accent" in resolved_theme.custom_props assert len(resolved_theme.translations_applied) == 1 ``` --- ## 7. MCP Integration Plan ### 7.1 New MCP Tools After the Python module is complete, add these MCP tools: ```python # tools/dss_mcp/integrations/translations.py from typing import Dict, Any, List, Optional from dss.translations import ( TranslationDictionaryLoader, TranslationDictionaryWriter, ThemeMerger, TokenResolver, TranslationValidator, TranslationSource, ) class TranslationIntegration: """MCP integration for translation dictionaries.""" async def translation_load_all( self, project_path: str ) -> Dict[str, Any]: """ Load all translation dictionaries for a project. MCP Tool: translation_load_all """ loader = TranslationDictionaryLoader(project_path) registry = await loader.load_all() return { "dictionaries": [ dict_obj.model_dump(mode='json') for dict_obj in registry.dictionaries.values() ], "total_token_mappings": len(registry.combined_token_map), "total_custom_props": len(registry.all_custom_props), "conflicts": registry.conflicts, } async def translation_resolve_token( self, project_path: str, source_token: str, source_type: Optional[str] = None ) -> Dict[str, Any]: """ Resolve a source token to DSS canonical. MCP Tool: translation_resolve_token """ loader = TranslationDictionaryLoader(project_path) registry = await loader.load_all() resolver = TokenResolver(registry) dss_path = resolver.resolve_to_dss(source_token, source_type) return { "source_token": source_token, "dss_path": dss_path, "found": dss_path is not None, } async def translation_create_dictionary( self, project_path: str, source: str, project_name: str, token_mappings: Optional[Dict[str, str]] = None, custom_props: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """ Create a new translation dictionary. MCP Tool: translation_create """ writer = TranslationDictionaryWriter(project_path) dictionary = await writer.create( source=TranslationSource(source), project=project_name, token_mappings=token_mappings, custom_props=custom_props, ) return { "success": True, "dictionary": dictionary.model_dump(mode='json'), "file_path": str(writer.translations_dir / f"{source}.json"), } async def translation_add_mapping( self, project_path: str, source: str, source_token: str, dss_token: str ) -> Dict[str, Any]: """ Add a token mapping to an existing dictionary. MCP Tool: translation_add_mapping """ writer = TranslationDictionaryWriter(project_path) await writer.add_mapping( source=TranslationSource(source), source_token=source_token, dss_token=dss_token, ) return { "success": True, "mapping": {source_token: dss_token}, } async def translation_validate( self, project_path: str, source: Optional[str] = None ) -> Dict[str, Any]: """ Validate translation dictionaries. MCP Tool: translation_validate """ loader = TranslationDictionaryLoader(project_path, validate=False) validator = TranslationValidator() results = [] if source: dictionary = await loader.load_dictionary(source) if dictionary: result = validator.validate_dictionary(dictionary.model_dump()) results.append({ "source": source, "is_valid": result.is_valid, "errors": [str(e) for e in result.errors], "warnings": [str(w) for w in result.warnings], }) else: for dict_file in loader.translations_dir.glob("*.json"): dictionary = await loader.load_dictionary_file(dict_file) result = validator.validate_dictionary(dictionary.model_dump()) results.append({ "source": dict_file.stem, "is_valid": result.is_valid, "errors": [str(e) for e in result.errors], "warnings": [str(w) for w in result.warnings], }) all_valid = all(r["is_valid"] for r in results) return { "all_valid": all_valid, "results": results, } async def translation_merge_theme( self, project_path: str, base_theme: str = "light", project_name: Optional[str] = None ) -> Dict[str, Any]: """ Merge base theme with translations and custom props. MCP Tool: translation_merge_theme """ loader = TranslationDictionaryLoader(project_path) registry = await loader.load_all() merger = ThemeMerger(registry) resolved = await merger.merge( base_theme=base_theme, project_name=project_name, ) return { "name": resolved.name, "base_theme": resolved.base_theme, "token_count": len(resolved.tokens), "custom_prop_count": len(resolved.custom_props), "translations_applied": resolved.translations_applied, "tokens": { path: token.model_dump(mode='json') for path, token in resolved.tokens.items() }, "custom_props": { path: token.model_dump(mode='json') for path, token in resolved.custom_props.items() }, } ``` ### 7.2 MCP Tool Manifest Update ```python # Add to tools/dss_mcp/handler.py TRANSLATION_TOOLS = [ { "name": "translation_load_all", "description": "Load all translation dictionaries for a project", "parameters": { "project_path": {"type": "string", "required": True} } }, { "name": "translation_resolve_token", "description": "Resolve a source token to DSS canonical path", "parameters": { "project_path": {"type": "string", "required": True}, "source_token": {"type": "string", "required": True}, "source_type": {"type": "string", "required": False} } }, { "name": "translation_create", "description": "Create a new translation dictionary", "parameters": { "project_path": {"type": "string", "required": True}, "source": {"type": "string", "required": True}, "project_name": {"type": "string", "required": True}, "token_mappings": {"type": "object", "required": False}, "custom_props": {"type": "object", "required": False} } }, { "name": "translation_add_mapping", "description": "Add a token mapping to a dictionary", "parameters": { "project_path": {"type": "string", "required": True}, "source": {"type": "string", "required": True}, "source_token": {"type": "string", "required": True}, "dss_token": {"type": "string", "required": True} } }, { "name": "translation_validate", "description": "Validate translation dictionaries", "parameters": { "project_path": {"type": "string", "required": True}, "source": {"type": "string", "required": False} } }, { "name": "translation_merge_theme", "description": "Merge base theme with translations and custom props", "parameters": { "project_path": {"type": "string", "required": True}, "base_theme": {"type": "string", "required": False, "default": "light"}, "project_name": {"type": "string", "required": False} } } ] ``` --- ## 8. Example Usage ### 8.1 Example Translation Dictionary - HeroUI ```json { "$schema": "dss-translation-v1", "project": "heroui-migration", "source": "heroui", "version": "1.0.0", "mappings": { "tokens": { "--heroui-primary-50": "color.primary.50", "--heroui-primary-100": "color.primary.100", "--heroui-primary-200": "color.primary.200", "--heroui-primary-300": "color.primary.300", "--heroui-primary-400": "color.primary.400", "--heroui-primary-500": "color.primary.500", "--heroui-primary-600": "color.primary.600", "--heroui-primary-700": "color.primary.700", "--heroui-primary-800": "color.primary.800", "--heroui-primary-900": "color.primary.900", "--heroui-content1": "color.neutral.50", "--heroui-content2": "color.neutral.100", "--heroui-content3": "color.neutral.200", "--heroui-content4": "color.neutral.300", "--heroui-radius-small": "border.radius.sm", "--heroui-radius-medium": "border.radius.md", "--heroui-radius-large": "border.radius.lg", "--heroui-shadow-small": "shadow.sm", "--heroui-shadow-medium": "shadow.md", "--heroui-shadow-large": "shadow.lg" }, "components": { "Button": "Button", "Card": "Card", "Input": "Input", "Modal": "Modal", "Dropdown": "Select" } }, "custom_props": {}, "unmapped": [], "notes": [ "HeroUI uses numeric scales - direct 1:1 mapping", "Content layers map to neutral scale" ] } ``` ### 8.2 Example Translation Dictionary - shadcn ```json { "$schema": "dss-translation-v1", "project": "shadcn-migration", "source": "shadcn", "version": "1.0.0", "mappings": { "tokens": { "--background": "color.background", "--foreground": "color.foreground", "--primary": "color.primary.500", "--primary-foreground": "color.primary.50", "--secondary": "color.secondary.500", "--secondary-foreground": "color.secondary.50", "--muted": "color.neutral.200", "--muted-foreground": "color.neutral.600", "--accent": "color.accent.500", "--accent-foreground": "color.accent.50", "--destructive": "color.danger.500", "--card": "color.neutral.50", "--card-foreground": "color.foreground", "--popover": "color.neutral.50", "--popover-foreground": "color.foreground", "--border": "color.border", "--input": "color.neutral.200", "--ring": "color.ring", "--radius": "border.radius.md" }, "components": { "Button": "Button", "Card": "Card", "Input": "Input", "Dialog": "Modal", "Popover": "Popover", "Select": "Select" } }, "custom_props": { "color.brand.shadcn.chart.1": "hsl(12 76% 61%)", "color.brand.shadcn.chart.2": "hsl(173 58% 39%)", "color.brand.shadcn.chart.3": "hsl(197 37% 24%)", "color.brand.shadcn.sidebar.primary": "var(--sidebar-primary)", "color.brand.shadcn.sidebar.accent": "var(--sidebar-accent)" }, "unmapped": [ "--chart-1", "--chart-2", "--chart-3", "--chart-4", "--chart-5" ], "notes": [ "shadcn is HEADLESS - no numeric color scales", "Semantic names expand to 500 (default) DSS values", "Chart colors are shadcn-specific, isolated in custom_props" ] } ``` ### 8.3 Example Python Usage ```python # Example: Complete workflow import asyncio from dss.translations import ( TranslationDictionaryLoader, TranslationDictionaryWriter, ThemeMerger, TokenResolver, TranslationSource, ) async def main(): project_path = "/path/to/my-project" # 1. Create translation dictionaries for project writer = TranslationDictionaryWriter(project_path) # Create CSS legacy mappings await writer.create( source=TranslationSource.CSS, project="my-project", token_mappings={ "--brand-primary": "color.primary.500", "--brand-secondary": "color.secondary.500", "--spacing-unit": "spacing.base", "--card-radius": "border.radius.md", }, custom_props={ "color.brand.myproject.gradient.start": "#FF6B6B", "color.brand.myproject.gradient.end": "#4ECDC4", }, notes=["Migrated from legacy CSS variables"] ) # 2. Load all dictionaries loader = TranslationDictionaryLoader(project_path) registry = await loader.load_all() print(f"Loaded {len(registry.dictionaries)} dictionaries") print(f"Total mappings: {len(registry.combined_token_map)}") print(f"Custom props: {len(registry.all_custom_props)}") # 3. Resolve tokens resolver = TokenResolver(registry) # Forward: source -> DSS dss_path = resolver.resolve_to_dss("--brand-primary") print(f"--brand-primary -> {dss_path}") # Reverse: DSS -> source source_token = resolver.resolve_to_source("color.primary.500", "css") print(f"color.primary.500 -> {source_token}") # 4. Merge with base theme merger = ThemeMerger(registry) resolved = await merger.merge( base_theme="light", project_name="my-project" ) print(f"\nResolved theme: {resolved.name}") print(f" Base: {resolved.base_theme}") print(f" Tokens: {len(resolved.tokens)}") print(f" Custom props: {len(resolved.custom_props)}") # 5. Export as Theme object (for Storybook integration) theme = merger.export_as_theme(resolved) print(f"\nExported Theme: {theme.name}") if __name__ == "__main__": asyncio.run(main()) ``` --- ## 9. Dependencies ### 9.1 Python Dependencies ``` # No new dependencies required! # Uses existing DSS dependencies: pydantic>=2.0.0 # Already in requirements.txt pydantic-settings>=2.0.0 # Already in requirements.txt ``` ### 9.2 Integration with Existing Modules | Module | Integration Type | Description | |--------|------------------|-------------| | `dss.models.theme` | Import | Use Theme, DesignToken models | | `dss.themes` | Import | Use default light/dark themes | | `dss.ingest` | Complement | Translations work with ingested tokens | | `dss.validators` | Pattern | Follow validation patterns | | `dss.storybook` | Consumer | Storybook uses merged themes | --- ## 10. Risks & Mitigations | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Schema changes break existing dictionaries | High | Medium | Version schema, provide migrations | | Performance with large dictionaries | Medium | Low | Implement caching, lazy loading | | Circular token references | High | Low | Detect cycles during resolution | | Conflict resolution ambiguity | Medium | Medium | Clear precedence rules, manual override | | MCP tool complexity | Medium | Medium | Simple API, comprehensive docs | --- ## 11. Success Criteria ### Phase 1 (Foundation) - [ ] All Pydantic models pass validation tests - [ ] Dictionary loader can read .dss/translations/*.json - [ ] Schema validation catches invalid dictionaries - [ ] 80%+ unit test coverage ### Phase 2 (Core) - [ ] Forward resolution works (source -> DSS) - [ ] Reverse resolution works (DSS -> source) - [ ] Theme merger produces valid themes - [ ] Writer can create/update dictionaries - [ ] Integration tests pass ### Phase 3 (Polish) - [ ] Pre-built dictionaries for HeroUI, shadcn, Tailwind - [ ] Semantic validation (not just schema) - [ ] 90%+ test coverage - [ ] Documentation complete - [ ] MCP integration working --- ## 12. Next Steps 1. **Review this plan** with core team 2. **Approve architecture** and data models 3. **Begin Phase 1** implementation 4. **Create GitHub issues** for tracking 5. **Set up CI/CD** for test automation --- ## Appendix A: DSS Config Schema ```json { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://dss.dev/schemas/config.schema.json", "title": "DSS Project Configuration", "description": "Project-level DSS configuration", "type": "object", "required": ["project", "version"], "properties": { "project": { "type": "string", "minLength": 1, "description": "Project identifier" }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "Configuration version" }, "base_theme": { "type": "string", "enum": ["light", "dark"], "default": "light", "description": "Base theme to use" }, "translations": { "type": "object", "properties": { "sources": { "type": "array", "items": { "type": "string", "enum": ["figma", "css", "scss", "heroui", "shadcn", "tailwind", "json"] }, "description": "Active translation sources" }, "auto_map": { "type": "boolean", "default": true, "description": "Enable automatic token mapping" } } }, "output": { "type": "object", "properties": { "formats": { "type": "array", "items": { "type": "string", "enum": ["css", "scss", "json", "typescript"] }, "description": "Output formats to generate" }, "dir": { "type": "string", "default": "dist/tokens", "description": "Output directory" } } } } } ``` --- **Document End** *This implementation plan provides a complete blueprint for the Translation Dictionary System. The architecture follows existing DSS patterns, uses Pydantic for data validation, and integrates seamlessly with the MCP plugin infrastructure.*