diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..71bc3a56 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,64 @@ +name: Publish documentation + +on: + push: + branches: ["main"] + paths: + - "docs/**" + - "mkdocs.yml" + - "requirements-docs.txt" + - ".github/workflows/docs.yml" + pull_request: + paths: + - "docs/**" + - "mkdocs.yml" + - "requirements-docs.txt" + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-docs.txt + + - name: Build documentation + run: mkdocs build --strict + + - name: Upload Pages artifact + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 575d9c64..b0a9300f 100644 --- a/.gitignore +++ b/.gitignore @@ -409,6 +409,7 @@ venv.bak/ # Generated output dist +site/ # PyCharm .idea/ @@ -426,4 +427,4 @@ test_docs/ # .DS Store .DS_Store -.python-version \ No newline at end of file +.python-version diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..58f7108d --- /dev/null +++ b/docs/api.md @@ -0,0 +1,176 @@ +# Python API + +The stable public Python API is intentionally small. The package exports `run_translation` from `co_op_translator.api`; most lower-level modules under `core`, `config`, and `utils` are implementation details used by the CLI and API entry point. + +```python +from co_op_translator.api import run_translation +``` + +## Public entry point + +::: co_op_translator.api.run_translation + +## Typical usage + +Translate Markdown files in the current project into Korean and Japanese: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ko ja", + markdown=True, +) +``` + +Translate only notebooks from a specific project root: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="fr", + root_dir="./my-course", + notebook=True, +) +``` + +Preview translation volume without writing files: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="es de", + root_dir="./my-course", + markdown=True, + dry_run=True, +) +``` + +Translate multiple content roots in one call: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ko", + markdown=True, + root_dirs=["./docs", "./labs"], +) +``` + +Write translations into explicit output groups: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ja", + markdown=True, + groups=[ + ("./course-a", "./localized/course-a"), + ("./course-b", "./localized/course-b"), + ], +) +``` + +Use a per-language placeholder when each language should contain a nested subdirectory: + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ko", + markdown=True, + groups=[ + ("./course", "./translations//course"), + ], +) +``` + +## Parameters + +| Parameter | Type | Default | Purpose | +| --- | --- | --- | --- | +| `language_codes` | `str` | Required | Space-separated target language codes, such as `"ko ja fr"`, or `"all"`. Alias codes are normalized to canonical BCP 47 values. | +| `root_dir` | `str` | `"."` | Project root for a single translation target. Ignored when `root_dirs` or `groups` are supplied. | +| `update` | `bool` | `False` | Delete and recreate existing translations for the selected languages. | +| `images` | `bool` | `False` | Include image translation. Requires Azure AI Vision configuration. | +| `markdown` | `bool` | `False` | Include Markdown translation. | +| `notebook` | `bool` | `False` | Include Jupyter notebook translation. | +| `debug` | `bool` | `False` | Enable debug logging. | +| `save_logs` | `bool` | `False` | Save DEBUG-level log files under the root `logs/` directory. | +| `yes` | `bool` | `True` | Auto-confirm prompts for programmatic and CI usage. | +| `add_disclaimer` | `bool` | `False` | Add machine translation disclaimers to translated Markdown and notebooks. | +| `translations_dir` | `str \| None` | `None` | Custom text translation output directory. Relative paths resolve against each root. | +| `image_dir` | `str \| None` | `None` | Custom translated image output directory. Relative paths resolve against each root. | +| `root_dirs` | `Iterable[str] \| None` | `None` | Multiple roots that share the same output settings. | +| `groups` | `Iterable[tuple[str, str \| None]] \| None` | `None` | Explicit `(root_dir, translations_dir)` pairs. Takes precedence over `root_dirs`. | +| `repo_url` | `str \| None` | `None` | Repository URL used when rendering README language table guidance. | +| `glossaries` | `Iterable[str] \| None` | `None` | Glossary terms to preserve during translation. Duplicates and blank terms are normalized. | +| `dry_run` | `bool` | `False` | Estimate translation volume and preview migration behavior without writing files. | + +If none of `markdown`, `notebook`, or `images` are set, the API translates all supported types: Markdown, notebooks, and images. + +## Configuration requirements + +`run_translation` checks configuration before translating: + +- An LLM provider is required. Configure either Azure OpenAI or OpenAI. +- Image translation requires Azure AI Vision in addition to the LLM provider. +- The API runs lightweight connectivity checks before translation begins. + +Required Azure OpenAI variables: + +```bash +AZURE_OPENAI_API_KEY="..." +AZURE_OPENAI_ENDPOINT="https://.openai.azure.com/" +AZURE_OPENAI_MODEL_NAME="gpt-4o" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_API_VERSION="2024-12-01-preview" +``` + +Required OpenAI variables: + +```bash +OPENAI_API_KEY="..." +OPENAI_CHAT_MODEL_ID="gpt-4o" +``` + +Required Azure AI Vision variables for image translation: + +```bash +AZURE_AI_SERVICE_API_KEY="..." +AZURE_AI_SERVICE_ENDPOINT="https://.cognitiveservices.azure.com/" +``` + +## Behavior notes + +- The API prints progress and estimate summaries through Click, matching the CLI user experience. +- `dry_run=True` computes estimates using virtual README updates, but does not write the README or translation files. +- `groups` are processed sequentially. A single aggregate estimate is printed before work begins. +- When image translation is selected, missing Vision configuration raises an error before translation starts. +- Existing alias-based language folders are detected and can be migrated to canonical language folder names as part of the run. + +## Internal call path + +The API delegates to the same core implementation used by the CLI: + +1. `co_op_translator.api.translation.run_translation` +2. `co_op_translator.config.Config`, `LLMConfig`, and `VisionConfig` +3. `co_op_translator.core.project.ProjectTranslator` +4. `co_op_translator.core.project.TranslationManager` +5. Markdown, notebook, text, and image translators under `co_op_translator.core` + +The following classes are useful for maintainers, but are not exported as the package-level stable API. + +| Class | Module | Responsibility | +| --- | --- | --- | +| `ProjectTranslator` | `co_op_translator.core.project.project_translator` | Coordinates project-level translation, directory management, per-language metadata normalization, and delegation to Markdown, notebook, and image translators. | +| `TranslationManager` | `co_op_translator.core.project.translation_manager` | Performs the async file processing work for Markdown, notebooks, images, stale detection, and translation metadata updates. | +| `ProjectEvaluator` | `co_op_translator.core.project.project_evaluator` | Finds translated Markdown pairs, evaluates translation quality, and reads confidence metadata for low-confidence repair workflows. | +| `LanguageFolderMigrator` | `co_op_translator.core.project.language_migrator` | Detects legacy alias language folders and prepares canonical BCP 47 folder migration plans. | +| `Config` | `co_op_translator.config.base_config` | Loads `.env` files and checks whether required LLM and optional Vision providers are configured. | +| `LLMConfig` | `co_op_translator.config.llm_config.config` | Auto-detects Azure OpenAI or OpenAI, validates required environment variables, and runs provider connectivity checks. | +| `VisionConfig` | `co_op_translator.config.vision_config.config` | Detects Azure AI Vision configuration and runs connectivity checks for image translation. | diff --git a/docs/assets/.gitkeep b/docs/assets/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/docs/assets/.gitkeep @@ -0,0 +1 @@ + diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 00000000..583deb1b Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 00000000..195d69a0 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,206 @@ +# CLI Reference + +Co-op Translator installs three command-line entry points: + +- `translate` +- `evaluate` +- `migrate-links` + +The dispatch logic lives in `co_op_translator.__main__`, which selects the command implementation based on the invoked script name. + +## translate + +Translate Markdown files, notebooks, and image text into one or more target languages. + +```bash +translate -l "ko ja fr" +``` + +### Common examples + +Translate only Markdown: + +```bash +translate -l "de" -md +``` + +Translate only notebooks: + +```bash +translate -l "zh-CN" -nb +``` + +Translate Markdown and images: + +```bash +translate -l "pt-BR" -md -img +``` + +Update existing translations by deleting and recreating them: + +```bash +translate -l "ko" -u +``` + +Run without interactive prompts: + +```bash +translate -l "ko ja" -md -y +``` + +Save logs: + +```bash +translate -l "ko" -s +``` + +### Options + +| Option | Required | Description | +| --- | --- | --- | +| `-l`, `--language-codes` | Yes | Space-separated language codes, such as `"es fr de"`, or `"all"`. | +| `-r`, `--root-dir` | No | Project root. Defaults to the current directory. | +| `-u`, `--update` | No | Delete existing translations for selected languages and recreate them. | +| `-img`, `--images` | No | Translate only image files. | +| `-md`, `--markdown` | No | Translate only Markdown files. | +| `-nb`, `--notebook` | No | Translate only Jupyter notebook files. | +| `-d`, `--debug` | No | Enable debug logging in the console. | +| `-s`, `--save-logs` | No | Save DEBUG-level logs under `/logs/`. | +| `-x`, `--fix` | No | Retranslate low-confidence Markdown files based on previous evaluation results. | +| `-c`, `--min-confidence` | No | Confidence threshold for `--fix`. Defaults to `0.7`. | +| `--add-disclaimer`, `--no-disclaimer` | No | Add or suppress machine translation disclaimers. Defaults to enabled in the CLI. | +| `-f`, `--fast` | No | Deprecated fast image mode. | +| `-y`, `--yes` | No | Auto-confirm prompts, useful in CI. | +| `--repo-url` | No | Repository URL used in the README languages table sparse-checkout advisory. | +| `--migrate-language-folders` | No | Rename legacy alias folders, such as `cn` or `tw`, to canonical BCP 47 folders. | +| `--dry-run` | No | Preview language folder migration and translation estimates without writing files. | + +If no type flag is provided, `translate` processes Markdown, notebooks, and images. Image translation requires Azure AI Vision configuration. + +## evaluate + +Evaluate translated Markdown quality for one language. + +```bash +evaluate -l "ko" +``` + +### Common examples + +Use a stricter low-confidence threshold: + +```bash +evaluate -l "es" -c 0.8 +``` + +Run rule-based checks only: + +```bash +evaluate -l "fr" -f +``` + +Run LLM-based checks only: + +```bash +evaluate -l "ja" -D +``` + +### Options + +| Option | Required | Description | +| --- | --- | --- | +| `-l`, `--language-code` | Yes | Single language code to evaluate. Alias codes are normalized. | +| `-r`, `--root-dir` | No | Project root. Defaults to the current directory. | +| `-c`, `--min-confidence` | No | Threshold used when listing low-confidence translations. Defaults to `0.7`. | +| `-d`, `--debug` | No | Enable debug logging. | +| `-s`, `--save-logs` | No | Save DEBUG-level logs under `/logs/`. | +| `-f`, `--fast` | No | Rule-based evaluation only. | +| `-D`, `--deep` | No | LLM-based evaluation only. | + +By default, `evaluate` uses both rule-based and LLM-based evaluation. Results are written into translation metadata and summarized in the console. + +## migrate-links + +Reprocess translated Markdown files and update notebook links so they point to translated notebooks when available. + +```bash +migrate-links -l "ko ja" +``` + +### Common examples + +Preview link updates: + +```bash +migrate-links -l "ko" --dry-run +``` + +Process all supported languages without confirmation: + +```bash +migrate-links -l "all" -y +``` + +Only rewrite links when translated notebooks exist: + +```bash +migrate-links -l "ko" --no-fallback-to-original +``` + +### Options + +| Option | Required | Description | +| --- | --- | --- | +| `-l`, `--language-codes` | Yes | Space-separated language codes, or `"all"`. | +| `-r`, `--root-dir` | No | Project root. Defaults to the current directory. | +| `--image-dir` | No | Translated image directory relative to the root. Defaults to `translated_images`. | +| `--dry-run` | No | Show files that would change without writing updates. | +| `--fallback-to-original`, `--no-fallback-to-original` | No | Use original notebook links when translated notebooks are missing. Enabled by default. | +| `-d`, `--debug` | No | Enable debug logging. | +| `-s`, `--save-logs` | No | Save DEBUG-level logs under `/logs/`. | +| `-y`, `--yes` | No | Auto-confirm prompts when processing all languages. | + +## Environment + +All commands require one configured LLM provider: + +```bash +# Azure OpenAI +AZURE_OPENAI_API_KEY="..." +AZURE_OPENAI_ENDPOINT="https://.openai.azure.com/" +AZURE_OPENAI_MODEL_NAME="gpt-4o" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_API_VERSION="2024-12-01-preview" + +# Or OpenAI +OPENAI_API_KEY="..." +OPENAI_CHAT_MODEL_ID="gpt-4o" +``` + +Image translation additionally requires Azure AI Vision: + +```bash +AZURE_AI_SERVICE_API_KEY="..." +AZURE_AI_SERVICE_ENDPOINT="https://.cognitiveservices.azure.com/" +``` + +## Output layout + +Text translations are written under: + +```text +translations// +``` + +Translated image output is written under: + +```text +translated_images// +``` + +For example, translating `README.md` and `docs/setup.md` into Korean produces: + +```text +translations/ko/README.md +translations/ko/docs/setup.md +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..b2d7e4a4 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,134 @@ +# Configuration + +Co-op Translator requires one language model provider. Image translation additionally requires Azure AI Vision. + +Configuration is read from environment variables. For local projects, place them in a `.env` file at the project root. + +## Local runtime setup + +Use a virtual environment before running the CLI locally. Co-op Translator supports Python 3.10 through 3.12. + +For normal CLI usage, install the published package inside a virtual environment: + +=== "Windows" + + ```powershell + python -m venv .venv + .venv\Scripts\activate + pip install co-op-translator + translate --help + ``` + +=== "macOS / Linux" + + ```bash + python -m venv .venv + source .venv/bin/activate + pip install co-op-translator + translate --help + ``` + +For repository development, install dependencies from the project root instead: + +```bash +poetry install +poetry run translate --help +``` + +After the CLI is available, configure one language model provider in `.env`. + +## Provider selection + +The tool auto-detects providers in this order: + +1. Azure OpenAI +2. OpenAI + +If neither provider is configured, `translate`, `evaluate`, `migrate-links`, and `run_translation` fail during configuration checks. + +## Azure OpenAI + +Use Azure OpenAI when your model is deployed in Azure AI Foundry or Azure OpenAI Service. + +```bash +AZURE_OPENAI_API_KEY="..." +AZURE_OPENAI_ENDPOINT="https://.openai.azure.com/" +AZURE_OPENAI_MODEL_NAME="gpt-4o" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_API_VERSION="2024-12-01-preview" +``` + +The connectivity check uses the endpoint, API key, API version, and deployment name before translation begins. + +## OpenAI + +Use OpenAI when calling the OpenAI API directly. + +```bash +OPENAI_API_KEY="..." +OPENAI_CHAT_MODEL_ID="gpt-4o" +OPENAI_ORG_ID="..." # optional +OPENAI_BASE_URL="..." # optional +``` + +`OPENAI_CHAT_MODEL_ID` is required because the translator needs an explicit chat model for API calls. + +## Azure AI Vision + +Image translation requires Azure AI Vision so the tool can extract text from images before translating it. + +```bash +AZURE_AI_SERVICE_API_KEY="..." +AZURE_AI_SERVICE_ENDPOINT="https://.cognitiveservices.azure.com/" +``` + +If image translation is selected with `-img`, `images=True`, or no content-type filter, the tool validates Vision configuration before translation starts. + +## Multiple credential sets + +The configuration layer supports multiple credential sets by suffixing variables with the same index: + +```bash +AZURE_OPENAI_API_KEY_1="..." +AZURE_OPENAI_ENDPOINT_1="https://.openai.azure.com/" +AZURE_OPENAI_MODEL_NAME_1="gpt-4o" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME_1="" +AZURE_OPENAI_API_VERSION_1="2024-12-01-preview" + +AZURE_OPENAI_API_KEY_2="..." +AZURE_OPENAI_ENDPOINT_2="https://.openai.azure.com/" +AZURE_OPENAI_MODEL_NAME_2="gpt-4o" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME_2="" +AZURE_OPENAI_API_VERSION_2="2024-12-01-preview" +``` + +Each set must be complete. The health check selects a working set before translation proceeds. + +## Command requirements + +| Command or API | LLM required | Vision required | Notes | +| --- | --- | --- | --- | +| `translate -md` | Yes | No | Translates Markdown only. | +| `translate -nb` | Yes | No | Translates notebooks only. | +| `translate -img` | Yes | Yes | Translates images only. | +| `translate` with no type flags | Yes | Yes | Default mode includes Markdown, notebooks, and images. | +| `evaluate` | Yes | No | Uses LLM evaluation unless `--fast` is selected. | +| `migrate-links` | Yes | No | Performs link migration, but still runs shared configuration checks. | +| `run_translation(markdown=True)` | Yes | No | Programmatic Markdown translation. | +| `run_translation(images=True)` | Yes | Yes | Programmatic image translation. | + +## Output directories + +Default text translation output: + +```text +translations// +``` + +Default translated image output: + +```text +translated_images// +``` + +The Python API can override these directories with `translations_dir` and `image_dir`. diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000..4ef882d4 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,128 @@ +# Examples + +These examples use the real CLI and Python API entry points from this repository. + +## Translate Markdown with the CLI + +```bash +translate -l "ko ja fr" -md +``` + +This translates Markdown files only and writes output under: + +```text +translations/ko/ +translations/ja/ +translations/fr/ +``` + +## Translate notebooks only + +```bash +translate -l "zh-CN" -nb +``` + +Notebook links can later be normalized with: + +```bash +migrate-links -l "zh-CN" +``` + +## Translate images only + +```bash +translate -l "pt-BR" -img +``` + +Image translation requires both an LLM provider and Azure AI Vision. + +## Preview without writing files + +```bash +translate -l "de es" -md --dry-run +``` + +Dry runs are useful when checking token estimates, migration plans, or CI wiring. + +## Repair low-confidence translations + +First evaluate translations: + +```bash +evaluate -l "ko" -c 0.8 +``` + +Then retranslate Markdown files below the threshold: + +```bash +translate -l "ko" --fix -c 0.8 -md +``` + +## Run from Python + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ko ja", + root_dir="./course", + markdown=True, + yes=True, +) +``` + +## Translate multiple roots from Python + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ko", + markdown=True, + root_dirs=[ + "./docs", + "./labs", + ], +) +``` + +## Use explicit output groups + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="ja", + markdown=True, + groups=[ + ("./course-a", "./localized/course-a"), + ("./course-b", "./localized/course-b"), + ], +) +``` + +## Preserve glossary terms + +```python +from co_op_translator.api import run_translation + +run_translation( + language_codes="fr", + markdown=True, + glossaries=[ + "Co-op Translator", + "Azure AI Foundry", + "GitHub Actions", + ], +) +``` + +Glossary terms are scoped to the API call and restored afterward. + +## CI-friendly translation command + +```bash +translate -l "ko ja" -md -y -s +``` + +This auto-confirms prompts and saves DEBUG-level logs under `logs/`. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..6107dcd8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,42 @@ +
+

Getting started

+

Co-op Translator Documentation

+

+ Configure an LLM provider, choose your target languages, and use Co-op + Translator from the CLI or Python API to keep translated project content + synchronized with the source. +

+ Start with the CLI -> +
+ +## Quick start + + diff --git a/docs/javascripts/tailwind-config.js b/docs/javascripts/tailwind-config.js new file mode 100644 index 00000000..036ddce6 --- /dev/null +++ b/docs/javascripts/tailwind-config.js @@ -0,0 +1,27 @@ +window.tailwind = window.tailwind || {}; +window.tailwind.config = { + prefix: "tw-", + darkMode: ["selector", '[data-md-color-scheme="slate"]'], + corePlugins: { + preflight: false, + }, + theme: { + extend: { + colors: { + coop: { + ink: "#172033", + blue: "#2563eb", + cyan: "#2563eb", + mint: "#2563eb", + soft: "#f4f8fb", + }, + }, + boxShadow: { + coop: "0 24px 70px rgba(15, 23, 42, 0.12)", + }, + fontFamily: { + display: ["Inter", "ui-sans-serif", "system-ui", "sans-serif"], + }, + }, + }, +}; diff --git a/docs/maintainer-guide.md b/docs/maintainer-guide.md new file mode 100644 index 00000000..4750c0cf --- /dev/null +++ b/docs/maintainer-guide.md @@ -0,0 +1,112 @@ +# Maintainer Guide + +This page summarizes how the API, CLI, and documentation site are wired together. + +## Public API boundary + +The stable Python API is exported from: + +```python +co_op_translator.api +``` + +Currently, that package exports: + +```python +from co_op_translator.api import run_translation +``` + +When adding new public APIs, update: + +- `src/co_op_translator/api/__init__.py` +- `docs/api.md` +- `tests/co_op_translator/test_api.py` + +Avoid documenting lower-level `core` modules as stable API unless the project intends to support them directly. + +## CLI entry points + +The package defines these Poetry scripts: + +```toml +[tool.poetry.scripts] +translate = "co_op_translator.__main__:main" +evaluate = "co_op_translator.__main__:main" +migrate-links = "co_op_translator.__main__:main" +``` + +`src/co_op_translator/__main__.py` dispatches by script name: + +- `translate` calls `co_op_translator.cli.translate.translate_command` +- `evaluate` calls `co_op_translator.cli.evaluate.evaluate_command` +- `migrate-links` calls `co_op_translator.cli.migrate_links.migrate_links_command` + +When adding or changing CLI options, update: + +- the relevant `src/co_op_translator/cli/*.py` command +- `getting_started/command-reference.md` +- `docs/cli.md` +- CLI-related tests, if behavior changes + +## Translation flow + +The high-level translation flow is: + +1. Parse CLI arguments or API parameters. +2. Validate LLM configuration with `LLMConfig`. +3. Validate Azure AI Vision when image translation is selected. +4. Normalize language codes. +5. Detect legacy language folder aliases. +6. Estimate translation volume. +7. Update README language/course sections when applicable. +8. Delegate project translation to `ProjectTranslator`. +9. `ProjectTranslator` delegates file processing to `TranslationManager`. + +## Documentation site + +The docs site is configured by: + +```text +mkdocs.yml +requirements-docs.txt +docs/ +``` + +Build locally: + +```bash +python -m pip install -r requirements-docs.txt +python -m mkdocs build --strict +``` + +Preview locally: + +```bash +python -m mkdocs serve +``` + +The generated site is written to `site/`, which is ignored by git. + +## GitHub Pages workflow + +`.github/workflows/docs.yml` builds the site on pull requests and deploys it on pushes to `main`. + +The workflow installs: + +```bash +pip install -r requirements.txt +pip install -r requirements-docs.txt +``` + +Installing runtime dependencies before docs dependencies lets `mkdocstrings` import the package and render the public Python API reference. + +## Docs quality bar + +Before merging documentation changes, run: + +```bash +python -m mkdocs build --strict +git diff --check +``` + +Use strict builds so broken links, invalid navigation entries, and API rendering issues fail early. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 00000000..052db660 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,636 @@ +:root { + --cot-blue: #2563eb; + --cot-cyan: #2563eb; + --cot-blue-soft: #3b82f6; + --cot-blue-dark: #1d4ed8; + --cot-blue-bright: #60a5fa; + --cot-ink: #172033; + --cot-soft: #f4f8fb; + --cot-border: #d8e2ec; +} + +[data-md-color-scheme="default"] { + --md-primary-fg-color: #2563eb; + --md-accent-fg-color: #2563eb; + --md-typeset-a-color: #1d4ed8; +} + +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #1d4ed8; + --md-accent-fg-color: #60a5fa; +} + +.md-header { + color: var(--cot-ink); + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 1px 0 rgba(15, 23, 42, 0.08); + backdrop-filter: blur(12px); +} + +[data-md-color-scheme="slate"] .md-header { + color: #f8fafc; + background: rgba(15, 23, 42, 0.96); + box-shadow: 0 1px 0 rgba(148, 163, 184, 0.18); +} + +.md-header__title { + font-weight: 700; +} + +.md-header__button, +.md-header__topic, +.md-header__title, +.md-search__icon { + color: inherit; +} + +.md-header__button.md-logo img, +.md-header__button.md-logo svg { + height: 1.7rem; + width: auto; +} + +.md-search__form { + color: var(--cot-ink); + background-color: #f8fafc; + border: 1px solid #cbd5e1; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); +} + +.md-search__form:hover { + background-color: #fff; + border-color: #94a3b8; +} + +.md-search__input { + color: var(--cot-ink); +} + +.md-search__input::placeholder { + color: #64748b; + opacity: 1; +} + +.md-search__icon, +.md-search__icon.md-icon { + color: #1e293b; +} + +.md-search__icon svg { + fill: #1e293b; +} + +.md-search__form:hover .md-search__icon, +.md-search__form:focus-within .md-search__icon { + color: var(--cot-blue); +} + +.md-search__form:hover .md-search__icon svg, +.md-search__form:focus-within .md-search__icon svg { + fill: var(--cot-blue); +} + +.md-search__form:focus-within { + background-color: #fff; + border-color: var(--cot-blue); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14); +} + +[data-md-color-scheme="slate"] .md-search__form { + color: #f8fafc; + background-color: rgba(15, 23, 42, 0.92); + border-color: rgba(148, 163, 184, 0.45); +} + +[data-md-color-scheme="slate"] .md-search__form:hover { + background-color: rgba(15, 23, 42, 1); + border-color: rgba(148, 163, 184, 0.72); +} + +[data-md-color-scheme="slate"] .md-search__input { + color: #f8fafc; +} + +[data-md-color-scheme="slate"] .md-search__input::placeholder { + color: #cbd5e1; +} + +[data-md-color-scheme="slate"] .md-search__icon, +[data-md-color-scheme="slate"] .md-search__icon.md-icon { + color: #cbd5e1; +} + +[data-md-color-scheme="slate"] .md-search__icon svg { + fill: #cbd5e1; +} + +[data-md-color-scheme="slate"] .md-search__form:hover .md-search__icon, +[data-md-color-scheme="slate"] .md-search__form:focus-within .md-search__icon { + color: var(--cot-blue-bright); +} + +[data-md-color-scheme="slate"] .md-search__form:hover .md-search__icon svg, +[data-md-color-scheme="slate"] .md-search__form:focus-within .md-search__icon svg { + fill: var(--cot-blue-bright); +} + +[data-md-color-scheme="slate"] .md-search__form:focus-within { + border-color: var(--cot-blue-bright); + box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); +} + +.md-nav--primary .md-nav__title[for="__drawer"] { + color: var(--cot-ink); + background: #f8fafc; + border-bottom: 1px solid #e2e8f0; +} + +.md-nav--primary .md-nav__title[for="__drawer"] .md-logo { + color: inherit; +} + +.md-nav--primary .md-nav__title[for="__drawer"] .md-logo img, +.md-nav--primary .md-nav__title[for="__drawer"] .md-logo svg { + height: 1.7rem; + width: auto; +} + +[data-md-color-scheme="slate"] .md-nav--primary .md-nav__title[for="__drawer"] { + color: #f8fafc; + background: #0f172a; + border-bottom-color: rgba(148, 163, 184, 0.2); +} + +.md-typeset h1, +.md-typeset h2 { + font-weight: 750; + letter-spacing: 0; +} + +.md-typeset h1 { + color: var(--cot-ink); +} + +[data-md-color-scheme="slate"] .md-typeset h1 { + color: var(--md-default-fg-color); +} + +.md-typeset code { + border-radius: 0.25rem; +} + +.md-typeset pre > code { + border-radius: 0.45rem; +} + +.md-typeset table:not([class]) { + border-radius: 0.5rem; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); +} + +@media screen and (min-width: 1220px) { + .md-grid { + max-width: 88rem; + } +} + +.md-sidebar--primary { + padding-left: 0.85rem; + border-right: 1px solid rgba(15, 23, 42, 0.08); +} + +.md-sidebar--primary .md-sidebar__scrollwrap { + margin-right: 0; + margin-left: 0; +} + +.md-sidebar--primary .md-logo { + display: none !important; +} + +.md-sidebar--secondary { + color: #667085; +} + +.md-nav__title { + color: #111827; + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0; + background: transparent; + box-shadow: none; +} + +.md-nav__item .md-nav__link--active { + color: var(--cot-blue); + font-weight: 650; +} + +.md-nav--primary > .md-nav__list > .md-nav__item > .md-nav__link--active { + position: relative; +} + +.md-nav--primary > .md-nav__list > .md-nav__item > .md-nav__link--active::before { + position: absolute; + top: 0.28rem; + bottom: 0.28rem; + left: -0.65rem; + width: 2px; + content: ""; + background: var(--cot-blue); + border-radius: 999px; +} + +[data-md-color-scheme="slate"] .md-sidebar--primary { + border-right-color: rgba(148, 163, 184, 0.14); +} + +[data-md-color-scheme="slate"] .md-nav__title { + color: #f8fafc; +} + +.md-sidebar--primary .md-nav--primary > .md-nav__list { + padding-left: 0.7rem; + border-left: 1px solid rgba(15, 23, 42, 0.08); +} + +.md-sidebar--primary .md-nav--primary .md-nav__title[for="__drawer"] { + display: flex; + align-items: flex-end; + height: 6.8rem; + padding: 0 0 1.25rem; + margin: 0 0 0.7rem; + overflow: hidden; + color: #111827; + background: transparent; + border: 0; + box-shadow: none; + font-size: 0; + line-height: 0; +} + +.md-sidebar--primary .md-nav--primary .md-nav__title[for="__drawer"] .md-logo { + padding: 0; + margin: 0; +} + +.md-sidebar--primary .md-nav--primary .md-nav__title[for="__drawer"] .md-logo img, +.md-sidebar--primary .md-nav--primary .md-nav__title[for="__drawer"] .md-logo svg { + width: auto; + max-width: 12.4rem; + height: 1.72rem; +} + +[data-md-color-scheme="slate"] .md-sidebar--primary .md-nav--primary > .md-nav__list { + border-left-color: rgba(148, 163, 184, 0.14); +} + +[data-md-color-scheme="slate"] .md-sidebar--primary .md-nav--primary .md-nav__title[for="__drawer"] { + color: #f8fafc; + background: transparent; +} + +.home-hero { + position: relative; + isolation: isolate; + margin-bottom: 3.6rem; + padding: 3.25rem 1.5rem 3.8rem; + overflow: hidden; + background: transparent; + border-bottom: 1px solid rgba(15, 23, 42, 0.07); + border-radius: 0; + box-shadow: none; +} + +.home-hero::before { + position: absolute; + inset: 0 -7rem auto -7rem; + z-index: -1; + height: 20rem; + pointer-events: none; + content: ""; + background: + linear-gradient(180deg, rgba(239, 246, 255, 0.78), rgba(255, 255, 255, 0) 88%), + linear-gradient(115deg, transparent 0 30%, rgba(37, 99, 235, 0.06) 30% 42%, transparent 42%), + linear-gradient(165deg, transparent 0 38%, rgba(37, 99, 235, 0.052) 38% 58%, transparent 58%), + linear-gradient(90deg, rgba(148, 163, 184, 0.1) 1px, transparent 1px), + linear-gradient(0deg, rgba(148, 163, 184, 0.1) 1px, transparent 1px); + background-size: auto, auto, auto, 56px 56px, 56px 56px; + mask-image: linear-gradient(to bottom, black, transparent 88%); +} + +.home-eyebrow { + margin: 0; + color: var(--cot-cyan); + font-size: 0.72rem; + font-weight: 850; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.home-hero h1 { + max-width: 34rem; + margin: 0.9rem 0 0; + color: var(--cot-ink); + font-size: clamp(2rem, 3vw, 2.45rem); + font-weight: 900; + line-height: 1.12; +} + +.home-lede { + max-width: 44rem; + margin: 1rem 0 0; + color: #5f6b7a; + font-size: 0.95rem; + line-height: 1.75; +} + +.home-link { + display: inline-flex; + margin-top: 1.25rem; + color: var(--cot-cyan); + font-size: 0.85rem; + font-weight: 850; + text-decoration: none; +} + +.home-link:hover { + color: var(--cot-blue); + text-decoration: none; +} + +.md-footer { + color: #667085; + background: #fff; + border-top: 1px solid rgba(15, 23, 42, 0.07); +} + +.md-footer-meta { + background: #fff; +} + +.md-footer__link { + color: #111827; +} + +.md-footer__direction { + color: #667085; +} + +[data-md-color-scheme="slate"] .md-footer, +[data-md-color-scheme="slate"] .md-footer-meta { + color: #cbd5e1; + background: #0f172a; + border-top-color: rgba(148, 163, 184, 0.14); +} + +[data-md-color-scheme="slate"] .md-footer__link { + color: #f8fafc; +} + +.quickstart-grid, +.resource-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1.6rem; + padding-top: 1.8rem; + margin-top: 1rem; + border-top: 1px solid rgba(15, 23, 42, 0.07); +} + +.quickstart-card { + display: flex; + flex-direction: column; + min-height: 10.5rem; + padding: 0.2rem 0; + color: inherit; + text-decoration: none; + background: transparent; + border: 0; + border-radius: 0.55rem; + box-shadow: none; + transition: color 180ms ease, transform 180ms ease; +} + +.quickstart-card:hover { + text-decoration: none; + transform: translateY(-2px); +} + +.quickstart-step { + align-self: flex-start; + padding: 0; + margin-bottom: 0.75rem; + color: var(--cot-blue); + font-size: 0.68rem; + font-weight: 900; + letter-spacing: 0.1em; + text-transform: uppercase; + background: transparent; + border-radius: 0; +} + +.quickstart-card strong, +.resource-card strong { + display: block; + color: #111827; + font-size: 0.86rem; + line-height: 1.5; +} + +.quickstart-card span:not(.quickstart-step), +.resource-card span:not(.resource-art) { + display: block; + margin-top: 0.55rem; + color: #667085; + font-size: 0.78rem; + line-height: 1.6; +} + +.quickstart-card em { + margin-top: auto; + color: var(--cot-cyan); + font-size: 0.76rem; + font-style: normal; + font-weight: 850; +} + +.quickstart-card:hover strong, +.quickstart-card:hover em { + color: var(--cot-blue-dark); +} + +.resource-card { + display: block; + overflow: hidden; + color: inherit; + text-decoration: none; + background: #fff; + border: 1px solid #dbe3ec; + border-radius: 1rem; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + transition: transform 180ms ease, box-shadow 180ms ease; +} + +.resource-card:hover { + text-decoration: none; + box-shadow: 0 18px 36px rgba(15, 23, 42, 0.1); + transform: translateY(-4px); +} + +.resource-art { + display: block; + height: 5.6rem; + padding: 1rem; + background: + linear-gradient(155deg, rgba(248, 250, 252, 1), rgba(239, 246, 255, 0.92)), + linear-gradient(90deg, rgba(148, 163, 184, 0.16) 1px, transparent 1px), + linear-gradient(0deg, rgba(148, 163, 184, 0.16) 1px, transparent 1px); + background-size: auto, 48px 48px, 48px 48px; + border-bottom: 1px solid #eef2f7; +} + +.resource-art b { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.2rem; + height: 2.2rem; + color: var(--cot-blue); + font-size: 0.62rem; + background: #fff; + border: 1px solid #cbd5e1; + border-radius: 999px; +} + +.resource-card > strong, +.resource-card > span:not(.resource-art) { + margin-right: 1.25rem; + margin-left: 1.25rem; +} + +.resource-card > strong { + margin-top: 1.25rem; +} + +.resource-card > span:not(.resource-art) { + margin-bottom: 1.25rem; +} + +[data-md-color-scheme="slate"] .home-hero { + background: transparent; + border-color: rgba(148, 163, 184, 0.14); + box-shadow: none; +} + +[data-md-color-scheme="slate"] .home-hero::before { + background: + linear-gradient(180deg, rgba(30, 41, 59, 0.52), rgba(15, 23, 42, 0) 88%), + linear-gradient(115deg, transparent 0 30%, rgba(96, 165, 250, 0.1) 30% 42%, transparent 42%), + linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px), + linear-gradient(0deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px); + background-size: auto, auto, 56px 56px, 56px 56px; +} + +[data-md-color-scheme="slate"] .home-hero h1, +[data-md-color-scheme="slate"] .quickstart-card strong, +[data-md-color-scheme="slate"] .resource-card strong { + color: #f8fafc; +} + +[data-md-color-scheme="slate"] .home-lede, +[data-md-color-scheme="slate"] .quickstart-card span:not(.quickstart-step), +[data-md-color-scheme="slate"] .resource-card span:not(.resource-art) { + color: #cbd5e1; +} + +[data-md-color-scheme="slate"] .quickstart-card, +[data-md-color-scheme="slate"] .resource-card { + background: transparent; + border-color: rgba(148, 163, 184, 0.24); +} + +[data-md-color-scheme="slate"] .quickstart-card:hover, +[data-md-color-scheme="slate"] .resource-card:hover { + border-color: var(--cot-blue-bright); + box-shadow: none; +} + +[data-md-color-scheme="slate"] .quickstart-step { + color: #bfdbfe; + background: transparent; +} + +[data-md-color-scheme="slate"] .resource-art { + background: + linear-gradient(155deg, rgba(15, 23, 42, 1), rgba(30, 41, 59, 0.82)), + linear-gradient(90deg, rgba(148, 163, 184, 0.12) 1px, transparent 1px), + linear-gradient(0deg, rgba(148, 163, 184, 0.12) 1px, transparent 1px); + background-size: auto, 48px 48px, 48px 48px; + border-bottom-color: rgba(148, 163, 184, 0.14); +} + +[data-md-color-scheme="slate"] .resource-art b { + color: #bfdbfe; + background: #020617; + border-color: rgba(148, 163, 184, 0.34); +} + +@media screen and (max-width: 960px) { + .quickstart-grid, + .resource-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media screen and (max-width: 82em) { + .md-nav--primary .md-nav__title[for="__drawer"] { + display: none; + height: 0; + padding: 0; + margin: 0; + font-size: 0; + line-height: 0; + border: 0; + } + + .md-sidebar--primary { + padding-left: 0; + border-right: 0; + } + + .md-sidebar--primary .md-nav--primary > .md-nav__list { + padding-left: 0; + border-left: 0; + } + + [data-md-color-scheme="slate"] .md-nav--primary .md-nav__title[for="__drawer"] { + border-bottom: 0; + } +} + +@media screen and (max-width: 640px) { + .home-hero { + max-width: calc(100vw - 2rem); + padding: 2.4rem 1rem 2.8rem; + } + + .home-hero::before { + inset: 0; + } + + .home-hero h1, + .home-lede { + max-width: 100%; + } + + .quickstart-grid, + .resource-grid { + grid-template-columns: 1fr; + max-width: calc(100vw - 2rem); + } + + .quickstart-card { + min-height: auto; + } +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..16445eb0 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,84 @@ +site_name: Co-op Translator +site_description: API and CLI documentation for Co-op Translator. +site_url: https://azure.github.io/co-op-translator/ +repo_url: https://github.com/Azure/co-op-translator +repo_name: Azure/co-op-translator + +docs_dir: docs +site_dir: site + +theme: + name: material + language: en + logo: assets/logo.png + favicon: assets/logo.png + icon: + repo: fontawesome/brands/github + features: + - navigation.instant + - navigation.sections + - navigation.top + - navigation.footer + - search.highlight + - search.suggest + - content.code.copy + - content.tabs.link + palette: + - scheme: default + primary: blue + accent: cyan + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + primary: blue + accent: cyan + toggle: + icon: material/weather-sunny + name: Switch to light mode + +extra_css: + - stylesheets/extra.css + +extra_javascript: + - javascripts/tailwind-config.js + - https://cdn.tailwindcss.com + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: + - src + options: + docstring_style: google + show_source: false + show_root_heading: true + show_signature_annotations: true + separate_signature: true + +markdown_extensions: + - admonition + - attr_list + - def_list + - md_in_html + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +nav: + - Home: index.md + - Configuration: configuration.md + - Examples: examples.md + - Python API: api.md + - CLI Reference: cli.md + - Maintainer Guide: maintainer-guide.md diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 00000000..19a23389 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,3 @@ +mkdocs>=1.6,<2.0 +mkdocs-material>=9.5,<9.6 +mkdocstrings[python]>=0.27,<1.0