#!/usr/bin/env python3 # name=skill_l10n.py # -*- coding: utf-8 -*- """ skill_l10n.py Context-aware localization tool for Agent Skills (SKILL.md + references + scripts). Usage: python skill_l10n.py [--src LANG] [--tgt LANG] [--mode replace|append] [--preserve-original-for-code yes|no] Environment: DEEPSEEK_API_TOKEN - required SKILL_L10N_VERIFY - 'true' or 'false' (default 'false') Dependencies: pip install openai httpx """ import os import sys import re import json import time import argparse import difflib from pathlib import Path from typing import Dict, Tuple, Optional from openai import OpenAI import httpx # ----------------------- # Configuration defaults # ----------------------- DEFAULT_MODEL = "deepseek-chat" CACHE_TTL = 24 * 3600 # 1 day # ----------------------- # Translator wrapper # ----------------------- class SmartTranslator: def __init__(self, token_env="DEEPSEEK_API_TOKEN", verify_ssl: bool = False, model=DEFAULT_MODEL): self.token = os.getenv(token_env, "") if not self.token: raise ValueError(f"Please set environment variable {token_env}") self.verify = verify_ssl self._httpx_client = httpx.Client(verify=self.verify) self._client = OpenAI(base_url="https://api.deepseek.com/v1", api_key=self.token, http_client=self._httpx_client) self.model = model self.cache: Dict[str, Tuple[float, dict]] = {} def close(self): try: self._httpx_client.close() except Exception: pass def _cache_get(self, key: str) -> Optional[dict]: ent = self.cache.get(key) if not ent: return None ts, val = ent if time.time() - ts > CACHE_TTL: del self.cache[key] return None return val def _cache_set(self, key: str, val: dict): self.cache[key] = (time.time(), val) def decide_and_translate(self, paragraph: str, context: str = "", source_language="auto", target_language="zh") -> dict: """ Ask model to decide whether to translate and return translation if should_translate. Return dict: { should_translate: bool, translated_text: str, reason: str } """ key = f"decide::{source_language}::{target_language}::{paragraph}::ctx::{context}" cached = self._cache_get(key) if cached: return cached system_msg = ( "You are a careful translator and content reviewer. " "Given a paragraph and surrounding context, return a JSON object with keys: " "should_translate (boolean), translated_text (string), reason (string). " "Do NOT translate code, CLI examples, file names, parameter names, or inline code. " "If you translate, preserve technical tokens and inline code unchanged." ) user_msg = f"Context:\n{context}\n\nParagraph:\n{paragraph}\n\nSource: {source_language}\nTarget: {target_language}" try: completion = self._client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_msg}, {"role": "user", "content": user_msg}, ], ) raw = completion.choices[0].message.content.strip() # try extract JSON first = raw.find("{") last = raw.rfind("}") json_text = raw[first:last+1] if first != -1 and last != -1 else raw data = json.loads(json_text) result = { "should_translate": bool(data.get("should_translate")), "translated_text": data.get("translated_text", "") or "", "reason": data.get("reason", "") or "", } except Exception as e: result = {"should_translate": False, "translated_text": "", "reason": f"api_error:{e}"} self._cache_set(key, result) return result # ----------------------- # File utilities # ----------------------- MD_FENCE_RE = re.compile(r'```[\s\S]*?```', flags=re.MULTILINE) INLINE_CODE_RE = re.compile(r'`[^`]+`') FRONTMATTER_RE = re.compile(r'^(---\n[\s\S]*?\n---\n)', flags=re.MULTILINE) COMMENT_PATTERNS = [ re.compile(r'^[ \t]*#'), re.compile(r'^[ \t]*//'), re.compile(r'^[ \t]*/\*'), re.compile(r'^[ \t]*\*'), re.compile(r'^[ \t]*--'), re.compile(r'^[ \t]*;'), re.compile(r'^[ \t]*REM ', re.I), re.compile(r'^[ \t]*