164 lines
4.7 KiB
Python
164 lines
4.7 KiB
Python
"""Key generation for translation strings."""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
class KeyGenerator:
|
|
"""Generate translation keys based on conventions."""
|
|
|
|
def __init__(
|
|
self,
|
|
style: str = "snake_case",
|
|
prefix: Optional[str] = None,
|
|
max_length: int = 100,
|
|
) -> None:
|
|
self.style = style
|
|
self.prefix = prefix
|
|
self.max_length = max_length
|
|
self._existing_keys: Dict[str, int] = {}
|
|
|
|
def set_existing_keys(self, keys: List[str]) -> None:
|
|
"""Set existing translation keys to avoid duplicates."""
|
|
self._existing_keys = {}
|
|
for key in keys:
|
|
if key in self._existing_keys:
|
|
self._existing_keys[key] += 1
|
|
else:
|
|
self._existing_keys[key] = 1
|
|
|
|
def generate_key(
|
|
self,
|
|
text: str,
|
|
file_path: Optional[Path] = None,
|
|
context: Optional[str] = None,
|
|
) -> str:
|
|
"""Generate a translation key for the given text."""
|
|
base_key = self._text_to_key(text)
|
|
|
|
if file_path:
|
|
path_prefix = self._path_to_prefix(file_path)
|
|
base_key = f"{path_prefix}_{base_key}"
|
|
|
|
if context:
|
|
context_key = self._text_to_key(context)
|
|
base_key = f"{base_key}_{context_key}"
|
|
|
|
if self.prefix:
|
|
base_key = f"{self.prefix}_{base_key}"
|
|
|
|
unique_key = self._make_unique(base_key)
|
|
|
|
return unique_key
|
|
|
|
def _text_to_key(self, text: str) -> str:
|
|
"""Convert text to a valid key format."""
|
|
normalized = text.lower().strip()
|
|
|
|
if self.style == "snake_case":
|
|
key = re.sub(r"[^a-z0-9]+", "_", normalized)
|
|
elif self.style == "kebab-case":
|
|
key = re.sub(r"[^a-z0-9]+", "-", normalized)
|
|
elif self.style == "camelCase":
|
|
key = self._to_camel_case(normalized)
|
|
elif self.style == "PascalCase":
|
|
key = self._to_pascal_case(normalized)
|
|
else:
|
|
key = re.sub(r"[^a-z0-9]+", "_", normalized)
|
|
|
|
key = key.strip("_").strip("-")
|
|
key = key[: self.max_length]
|
|
|
|
return key
|
|
|
|
def _path_to_prefix(self, file_path: Path) -> str:
|
|
"""Convert file path to a key prefix."""
|
|
parts = []
|
|
|
|
for part in file_path.parts:
|
|
part = re.sub(r"[^a-z0-9]+", "_", part.lower())
|
|
part = part.strip("_")
|
|
if part and part not in ("src", "src", "app", "components", "pages", "views"):
|
|
parts.append(part)
|
|
|
|
return "_".join(parts)
|
|
|
|
def _to_camel_case(self, text: str) -> str:
|
|
"""Convert text to camelCase."""
|
|
words = re.split(r"[^a-z0-9]+", text.lower())
|
|
words = [w for w in words if w]
|
|
|
|
if not words:
|
|
return ""
|
|
|
|
first = words[0]
|
|
rest = [w.capitalize() for w in words[1:]]
|
|
|
|
return first + "".join(rest)
|
|
|
|
def _to_pascal_case(self, text: str) -> str:
|
|
"""Convert text to PascalCase."""
|
|
words = re.split(r"[^a-z0-9]+", text.lower())
|
|
words = [w.capitalize() for w in words if w]
|
|
|
|
return "".join(words)
|
|
|
|
def _make_unique(self, key: str) -> str:
|
|
"""Ensure key is unique by appending a number if needed."""
|
|
if key not in self._existing_keys:
|
|
self._existing_keys[key] = 0
|
|
return key
|
|
|
|
count = self._existing_keys[key]
|
|
self._existing_keys[key] = count + 1
|
|
|
|
suffix = f"_{count + 1}"
|
|
new_key = key + suffix
|
|
|
|
while new_key in self._existing_keys:
|
|
count += 1
|
|
suffix = f"_{count}"
|
|
new_key = key + suffix
|
|
|
|
self._existing_keys[new_key] = 0
|
|
return new_key
|
|
|
|
|
|
def analyze_existing_keys(translation_file: Path) -> List[str]:
|
|
"""Analyze existing translation keys from a file."""
|
|
keys: List[str] = []
|
|
|
|
try:
|
|
content = translation_file.read_text(encoding="utf-8")
|
|
except (OSError, UnicodeDecodeError):
|
|
return keys
|
|
|
|
if translation_file.suffix == ".json":
|
|
import json
|
|
|
|
try:
|
|
data = json.loads(content)
|
|
if isinstance(data, dict):
|
|
keys.extend(_flatten_dict(data))
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
elif translation_file.suffix in (".po", ".pot"):
|
|
for match in re.finditer(r'^msgid\s+"([^"]+)"', content, re.MULTILINE):
|
|
keys.append(match.group(1))
|
|
|
|
return keys
|
|
|
|
|
|
def _flatten_dict(d: dict, prefix: str = "") -> List[str]:
|
|
"""Flatten a nested dictionary to get all keys."""
|
|
keys = []
|
|
for k, v in d.items():
|
|
full_key = f"{prefix}.{k}" if prefix else k
|
|
if isinstance(v, dict):
|
|
keys.extend(_flatten_dict(v, full_key))
|
|
else:
|
|
keys.append(full_key)
|
|
return keys
|