diff --git a/AGENTS.md b/AGENTS.md index 3f2c8f7..5cb8c9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,8 +5,9 @@ See [README.md](./README.md) for full project overview. ## Installation -End-user install methods (pipx, npm, one-liner script, manual download) are documented in -[README.md](./README.md). The install script lives at [script/install.sh](./script/install.sh). +End-user install methods (Homebrew, pipx, npm, one-liner scripts, manual download) are documented +in [README.md](./README.md). The install scripts live at [script/install.sh](./script/install.sh) +(macOS/Linux) and [script/install.ps1](./script/install.ps1) (Windows PowerShell). For local development, build from source with `make build` (see below) or: @@ -18,7 +19,7 @@ go install github.com/min0625/mint/cmd/mint@latest # Go 1.26.4+; binary → $G ```bash make build # compile → bin/mint (CGO_ENABLED=0, trimpath, ldflags injected) -make test # go test -race -failfast -v ./... +make test # go test -race -failfast ./... make lint # golangci-lint (only new violations since HEAD) make fix # golangci-lint --fix + go mod tidy make check # check-tidy + lint + test (CI gate) @@ -26,8 +27,9 @@ make check-tidy # verify go.mod/go.sum are tidy make release-snapshot # goreleaser release --snapshot --clean (test release locally) ``` -Tool versions are pinned in [mise.toml](./mise.toml) (Go 1.26.4, golangci-lint 2.12.2, goreleaser 2.16.0). -Run `mise install` to set up the exact toolchain. +Tool versions are pinned in [mise.toml](./mise.toml) (Go 1.26.4, golangci-lint 2.12.2, goreleaser 2.16.0, prek 0.4.6). +Run `mise install` to set up the exact toolchain. Pre-commit hooks run `make check` via +`prek` (config: [.pre-commit-config.yaml](./.pre-commit-config.yaml)). ## Distribution @@ -37,7 +39,10 @@ live under [pypi/](./pypi/) (`build_wheels.py`) and [npm/](./npm/). For local wh steps see [pypi/](./pypi/). **Release trigger:** push a tag matching `v*.*.*` → `release.yml` builds multi-platform -binaries → `publish-pypi.yml` assembles wheels and uploads to PyPI. +binaries → `publish-pypi.yml` assembles wheels and uploads to PyPI, and `publish-npm.yml` +publishes the npm packages (both run on `workflow_run` after Release succeeds). The Homebrew +cask in `min0625/homebrew-tap` is updated by goreleaser itself (see `homebrew_casks` in +[.goreleaser.yaml](./.goreleaser.yaml)). ## Project Layout @@ -52,6 +57,10 @@ internal/provider/ googlegenai/google_genai.go # Google Gemini HTTP client (implements Completer) openai/openai.go # OpenAI GPT HTTP client (implements Completer) anthropic/anthropic.go # Anthropic Claude HTTP client (implements Completer) +script/install.sh, install.ps1 # end-user one-liner install scripts (macOS/Linux, Windows) +docs/manual-testing.md # manual test cases for humans and AI agents +npm/, pypi/ # npm / PyPI packaging that wraps the released binaries +testdata/sample.txt # long-form input used in tests and token-usage measurements bin/mint # compiled binary (gitignored) .goreleaser.yaml # GoReleaser multi-platform release configuration .github/workflows/release.yml # GitHub Actions: triggered on v*.*.* tag push @@ -63,7 +72,7 @@ bin/mint # compiled binary (gitignored) |----------|-------------|----------|---------| | `MINT_PROVIDER` | LLM provider: `google-genai`, `openai`, `anthropic` | Yes | — | | `MINT_API_KEY` | API key for the chosen provider; optional when `MINT_BASE_URL` is set | Conditional* | — | -| `MINT_BASE_URL` | Custom API base URL (domain only; each provider appends its own path); use with `openai` to target local inference servers — Ollama (`http://localhost:11434`) or LM Studio (`http://localhost:1234`); optional for cloud providers to point to a proxy | Conditional* | Provider default | +| `MINT_BASE_URL` | Custom API base URL (domain only; each provider appends its own path); use with `openai` to target any OpenAI-compatible endpoint — local servers (Ollama `http://localhost:11434`, LM Studio `http://localhost:1234`, llama.cpp `http://localhost:8080`) or hosted services (OpenRouter, Groq, DeepSeek); optional for cloud providers to point to a proxy | Conditional* | Provider default | | `MINT_MODEL_NAME` | LLM model name to use | Required when `MINT_BASE_URL` is set; optional otherwise | Provider default** | | `MINT_TARGET_LANG` | Target language(s) - single or comma-separated (e.g. `en`, `en,zh-TW,ja`) | Optional | System locale or `en` | | `MINT_VERBOSE` | Set to `true` to enable verbose diagnostic output to stderr (equivalent to `--verbose`) | Optional | `false` | @@ -73,6 +82,7 @@ bin/mint # compiled binary (gitignored) ## Documentation +- **Manual test cases** — [docs/manual-testing.md](./docs/manual-testing.md) lists end-to-end CLI cases (with expected output and verbose diagnostics) that AI agents and humans can run to verify behavior; keep it in sync when CLI behavior, flags, or error messages change. - **Multilingual README sync** — **all** README variants must be kept in sync: `README.md` (English, the canonical source) and every `README..md` translation. [LANGUAGES.md](./LANGUAGES.md) is the authoritative list of variants — consult it first to confirm the full set, since new languages are added over time. Whenever one README is updated, apply the equivalent change to *every* other variant listed there. New README language variants follow the pattern `README..md`. - **Language list** — the list of available languages lives **only** in [LANGUAGES.md](./LANGUAGES.md). Each README links to it with a single static line, written **in that README's own language** (e.g. English: `🌐 Other languages`; Traditional Chinese: `🌐 其他語言`). This line never changes when languages are added. To add a language: create `README..md` and add one entry to `LANGUAGES.md` — do **not** add a per-language switcher row to every README. - **Absolute URLs in README headers** — `README.md` is shipped as the PyPI long-description and the npm package readme. PyPI does **not** rewrite relative links, so the `LANGUAGES.md` link in each README must be an **absolute** GitHub URL (`https://github.com/min0625/mint/blob/main/LANGUAGES.md`). Links *inside* `LANGUAGES.md` may stay relative — that file is GitHub-only (not packaged into PyPI/npm). diff --git a/LANGUAGES.md b/LANGUAGES.md index 804f687..310d876 100644 --- a/LANGUAGES.md +++ b/LANGUAGES.md @@ -3,8 +3,10 @@ Mint's README is available in the following languages: - [English](README.md) +- [简体中文 (Simplified Chinese)](README.zh-CN.md) - [繁體中文 (Traditional Chinese)](README.zh-TW.md) - [日本語 (Japanese)](README.ja.md) +- [한국어 (Korean)](README.ko.md) > Adding a new language? Create `README..md` and add one line to this file — > the existing READMEs link here and don't need to change. diff --git a/README.ja.md b/README.ja.md index 132744e..6a71ba8 100644 --- a/README.ja.md +++ b/README.ja.md @@ -25,7 +25,7 @@ cat document.txt | mint -t fr # ファイル全体を翻訳 ## ✨ Mintの特徴 - **ゼロ設定** — 単一の実行ファイル。APIキーは環境変数で管理するため、設定ファイルを汚しません。 -- **マルチプロバイダー** — Google Gemini、OpenAI、Anthropicのほか、ローカルのOllamaやLM Studioにも対応。 +- **マルチプロバイダー** — Google Gemini、OpenAI、Anthropicに加え、OpenAI互換エンドポイント(Ollama、LM Studio、OpenRouter、Groq、DeepSeek、llama.cppなど)にも対応。 - **スマート検出** — 実行のたびに言語を自動検出。言語に依存しない内容(数字、記号)はそのまま出力します。 - **スマート修正** — 入力言語とターゲット言語が同じ場合は、翻訳ではなく文法やスペルの修正を行います。 - **ストリーミング出力** — レスポンスをリアルタイムでストリーミングするため、長文翻訳でも待たされません。 @@ -108,6 +108,29 @@ export MINT_MODEL_NAME=qwen2.5:7b # Ollamaでロード済みのモデル名に export MINT_PROVIDER=openai export MINT_BASE_URL=http://localhost:1234 export MINT_MODEL_NAME=lmstudio-community/Qwen2.5-7B-Instruct-GGUF # LM Studioでロード済みのモデル名に変更 + +# llama.cpp の llama-server(APIキー不要) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:8080 +export MINT_MODEL_NAME=qwen2.5:7b # llama-serverでロード済みのモデル名に変更 + +# OpenRouter(1つのキーで数百のモデルを利用可能 — https://openrouter.ai/models) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://openrouter.ai/api +export MINT_API_KEY=sk-or-... +export MINT_MODEL_NAME=openai/gpt-4o-mini + +# Groq(高速推論、無料枠あり) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.groq.com/openai +export MINT_API_KEY=gsk_... +export MINT_MODEL_NAME=llama-3.1-8b-instant + +# DeepSeek +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.deepseek.com +export MINT_API_KEY=sk-... +export MINT_MODEL_NAME=deepseek-chat ``` ### 2. 翻訳の実行 @@ -125,7 +148,6 @@ cat document.txt | mint -t zh-TW ```bash mint -t ja -v "Good morning" # [mint] provider: google-genai -# [mint] model: gemini-3.1-flash-lite # [mint] single target — skipping language detection # [mint] target language: ja # おはようございます @@ -204,7 +226,7 @@ mint "こんにちは" # ja → en: Hello | `MINT_API_KEY` | APIキー。デフォルトのエンドポイント使用時は必須。`MINT_BASE_URL`設定時は任意(プロキシ側で認証処理する場合) | — | | `MINT_BASE_URL` | カスタムAPIベースURL(ドメインのみ指定、パスは各プロバイダーが自動付与)。`openai`と組み合わせることで、Ollama(`http://localhost:11434`)、LM Studio(`http://localhost:1234`)、またはOpenAI互換エンドポイントを指定可能 | プロバイダーのデフォルト | | `MINT_MODEL_NAME` | 使用するモデル名(`MINT_BASE_URL` 設定時は必須) | `gemini-3.1-flash-lite` / `gpt-4o-mini` / `claude-haiku-4-5` | -| `MINT_TARGET_LANG` | ターゲット言語(例: `en` または `en,zh-TW,ja`) | システムのロケール設定 | +| `MINT_TARGET_LANG` | ターゲット言語(例: `en` または `en,zh-TW,ja`) | システムのロケール設定(なければ `en`) | | `MINT_VERBOSE` | `true`に設定すると詳細な診断出力が有効になります(`--verbose`相当) | `false` | --- @@ -222,7 +244,7 @@ mint "こんにちは" # ja → en: Hello ## 📅 ロードマップ -- [x] 複数のLLMプロバイダー対応(Google Gemini、OpenAI、Anthropic、ローカルのOllama / LM Studio) +- [x] 複数のLLMプロバイダー対応(Google Gemini、OpenAI、Anthropic、およびOpenAI互換エンドポイント) - [x] `MINT_TARGET_LANG` による言語自動検出と多言語ローテーション - [x] `--target` / `-t` フラグによるターゲット言語の明示的指定 - [x] `--source` / `-s` フラグによるソース言語の明示的指定 diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 0000000..989d553 --- /dev/null +++ b/README.ko.md @@ -0,0 +1,262 @@ +🌐 [다른 언어](https://github.com/min0625/mint/blob/main/LANGUAGES.md) + +# 🌿 Mint + +> Minimalist AI Translation CLI — 심플하게. 빠르게. 직관적으로. + +[![GitHub Release](https://img.shields.io/github/v/release/min0625/mint?logo=github)](https://github.com/min0625/mint/releases) +[![PyPI](https://img.shields.io/pypi/v/mint-ai?logo=pypi&logoColor=white)](https://pypi.org/project/mint-ai/) +[![npm](https://img.shields.io/npm/v/mint-ai?logo=npm)](https://www.npmjs.com/package/mint-ai) +[![codecov](https://codecov.io/gh/min0625/mint/branch/main/graph/badge.svg)](https://codecov.io/gh/min0625/mint) + +Mint는 단일 실행 파일로 동작하는 LLM 기반 번역 CLI입니다. 환경 변수 두 개만 설정하면 파일, 파이프 출력, 직접 입력한 텍스트 등 무엇이든 명령줄에서 번역할 수 있습니다. 언어 자동 감지, 문법 교정, 스트리밍 출력, 다국어 순환 기능을 기본으로 제공합니다. + +```bash +export MINT_PROVIDER=google-genai +export MINT_API_KEY=your_key + +mint -t ja "Good morning" # おはようございます +echo "早安" | mint -t en # Good morning +cat document.txt | mint -t fr # 파일 전체 번역 +``` + +--- + +## ✨ 왜 Mint인가? + +- **제로 설정** — 단일 실행 파일; API 키는 환경 변수로 관리하여 설정 파일이 지저분해지지 않음 +- **멀티 프로바이더** — Google Gemini, OpenAI, Anthropic은 물론, OpenAI 호환 엔드포인트(Ollama, LM Studio, OpenRouter, Groq, DeepSeek, llama.cpp 등)도 지원 +- **스마트 감지** — 호출할 때마다 언어를 자동 감지; 숫자·기호 같은 언어 중립적 콘텐츠는 그대로 출력 +- **스마트 교정** — 입력 언어와 목표 언어가 같다면 번역 대신 문법과 철자를 자동 교정 +- **스트리밍** — 응답을 실시간으로 스트리밍하여 긴 번역도 기다릴 필요 없음 +- **조합 가능** — stdin/stdout에 친화적인 설계로 `grep`, `sed`, `xargs` 등과 매끄럽게 연동 +- **보안** — system/user 메시지 분리와 요청마다 생성되는 무작위 nonce 구분자를 통해 신뢰할 수 없는 입력을 모델 지시로부터 격리; 악의적인 콘텐츠를 번역해도 LLM의 동작을 탈취할 수 없음 + +--- + +## 📋 설치 + +### 자동 설치 (권장) + +**macOS / Linux** + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/min0625/mint/main/script/install.sh)" +``` + +OS와 아키텍처(Linux/macOS, x86_64/arm64)를 자동 감지하여 `~/.local/bin`에 설치합니다. `MINT_INSTALL_DIR`로 설치 경로를 변경하거나 `MINT_VERSION=v1.0.0`으로 버전을 지정할 수 있습니다. + +**Windows (PowerShell)** + +```powershell +irm https://raw.githubusercontent.com/min0625/mint/main/script/install.ps1 | iex +``` + +아키텍처(x86_64/arm64)를 자동 감지하여 `$HOME\.local\bin`에 설치합니다. `$env:MINT_INSTALL_DIR`로 설치 경로를 변경하거나 `$env:MINT_VERSION = 'v1.0.0'`으로 버전을 지정할 수 있습니다. + +### Homebrew (macOS / Linux) + +```bash +brew install min0625/tap/mint-ai +``` + +### pipx + +```bash +pipx install mint-ai +``` + +### npm + +```bash +npm install -g mint-ai +``` + +### 수동 다운로드 + +[GitHub Releases](https://github.com/min0625/mint/releases)에서 사용 중인 플랫폼용 사전 빌드 바이너리를 다운로드한 뒤 `PATH`에 포함된 디렉터리로 옮기고 다음 명령으로 확인합니다: + +```bash +mint --version +``` + +--- + +## 🚀 빠른 시작 + +### 1. 프로바이더 설정 + +```bash +# Google Gemini (무료 등급 제공 — https://aistudio.google.com/apikey) +export MINT_PROVIDER=google-genai +export MINT_API_KEY=your_gemini_api_key + +# OpenAI +export MINT_PROVIDER=openai +export MINT_API_KEY=sk-... + +# Anthropic +export MINT_PROVIDER=anthropic +export MINT_API_KEY=sk-ant-... + +# Ollama (API 키 불필요) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:11434 +export MINT_MODEL_NAME=qwen2.5:7b # Ollama에 로드된 원하는 모델로 변경 + +# LM Studio (API 키 불필요) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:1234 +export MINT_MODEL_NAME=lmstudio-community/Qwen2.5-7B-Instruct-GGUF # LM Studio에 로드된 원하는 모델로 변경 + +# llama.cpp의 llama-server (API 키 불필요) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:8080 +export MINT_MODEL_NAME=qwen2.5:7b # llama-server에 로드된 모델과 일치시키기 + +# OpenRouter (키 하나로 수백 개 모델 사용 — https://openrouter.ai/models) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://openrouter.ai/api +export MINT_API_KEY=sk-or-... +export MINT_MODEL_NAME=openai/gpt-4o-mini + +# Groq (빠른 추론 속도, 무료 등급 제공) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.groq.com/openai +export MINT_API_KEY=gsk_... +export MINT_MODEL_NAME=llama-3.1-8b-instant + +# DeepSeek +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.deepseek.com +export MINT_API_KEY=sk-... +export MINT_MODEL_NAME=deepseek-chat +``` + +### 2. 번역하기 + +```bash +mint --target ja "Good morning" +mint -t zh-TW "Good morning" + +echo "The quick brown fox" | mint -t fr +cat document.txt | mint -t zh-TW +``` + +`--verbose` / `-v` (또는 `MINT_VERBOSE=true`)를 사용하면 진단 정보와 토큰 사용량이 stderr로 출력됩니다: + +```bash +mint -t ja -v "Good morning" +# [mint] provider: google-genai +# [mint] single target — skipping language detection +# [mint] target language: ja +# おはようございます +# [mint] tokens: 113 in / 2 out +``` + +**대표적인 토큰 사용량** (`gemini-3.1-flash-lite` 기준 실측): + +| 모드 | 입력 | 호출 횟수 | 입력 토큰 | 출력 토큰 | +|------|------|----------|----------|----------| +| 단일 목표 (`-t` 또는 단일 `MINT_TARGET_LANG`) | 짧은 단어/문장 | 1 | ~110–130 | ~1–15 | +| 단일 목표 | 긴 글 (`testdata/sample.txt`) | 1 | ~465–470 | ~450–560 | +| 다중 목표 순환 (쉼표로 구분된 `MINT_TARGET_LANG`) | 짧은 문장 | 2 | ~250–260 | ~2–8 | +| 명시적 소스 `-s` + 순환 | 짧은 문장 | 1 | ~105–120 | ~1–2 | + +> 토큰 수는 입력 길이에 비례해 증가합니다. 출력 토큰은 목표 언어에 따라 달라지며, 일본어와 중국어는 동일한 내용이라도 영어보다 더 많은 토큰을 생성하는 경향이 있습니다. + +**100만 토큰으로 몇 번이나 번역할 수 있을까?** (입력+출력 합산, 위 실측값에서 산출): + +| 입력 | 번역당 약 토큰 수 | 100만 토큰당 번역 횟수 | +|------|-----------------|---------------------| +| 짧은 단어나 구문 | 약 120 | 약 8,000회 | +| 300단어 분량 글 | 약 1,000 | 약 1,000회 | + +> 수치는 입력과 출력 토큰을 합산한 것입니다. 프로바이더마다 입력과 출력 요금이 다르며 무료 등급을 제공하는 곳도 많으니, 정확한 요금은 각 프로바이더의 요금 페이지를 확인하세요. Google Gemini는 [Google AI Studio](https://aistudio.google.com/apikey)의 무료 등급을 신용카드 없이 이용할 수 있습니다. + +`--source` / `-s`로 **소스 언어를 강제 지정**하면 목표 언어에서도 유효한 입력(언어 간 동형이의어, 로마자 표기)을 번역할 수 있습니다: + +```bash +mint -s fr -t en "pain" # 프랑스어 → bread (-s 없으면 영어 "pain"으로 처리됨) +mint -s ja -t en "konnichiwa" # 로마자 일본어 → hello +``` + +### 3. 스마트 언어 감지 + +**자동 감지 후 번역:** + +```bash +export MINT_TARGET_LANG=en + +mint "早安" # 중국어로 감지 → Good morning +``` + +**문법 및 철자 교정** — 입력 언어와 목표 언어가 같으면 번역 대신 교정합니다: + +```bash +export MINT_TARGET_LANG=en + +mint "Good mooorning" # 영어로 감지 → Good morning +mint "She don't know nothing" # 영어로 감지 → She doesn't know anything +mint "i luv coding" # 영어로 감지 → I love coding +``` + +**언어 순환** — 목록의 다음 언어로 번역하며, 마지막에 도달하면 처음으로 돌아갑니다: + +```bash +# 언어 2개 +export MINT_TARGET_LANG=en,zh-TW +mint "Hello" # en → zh-TW: 你好 +mint "你好" # zh-TW → en: Hello + +# 언어 3개 +export MINT_TARGET_LANG=en,zh-TW,ja +mint "Hello" # en → zh-TW: 你好 +mint "你好" # zh-TW → ja: こんにちは +mint "こんにちは" # ja → en: Hello +``` + +--- + +## 🔑 환경 변수 + +| 변수 | 설명 | 기본값 | +|------|------|--------| +| `MINT_PROVIDER` | `google-genai` \| `openai` \| `anthropic` | — (필수) | +| `MINT_API_KEY` | API 키; 기본 엔드포인트 사용 시 필수, `MINT_BASE_URL` 설정 시 선택 (프록시가 인증을 처리하는 경우) | — | +| `MINT_BASE_URL` | 사용자 지정 API base URL (도메인만 입력, 경로는 각 프로바이더가 자동으로 붙임); `openai`와 함께 사용해 Ollama (`http://localhost:11434`), LM Studio (`http://localhost:1234`) 또는 그 밖의 OpenAI 호환 엔드포인트를 지정 가능 | 프로바이더 기본값 | +| `MINT_MODEL_NAME` | 사용할 모델; `MINT_BASE_URL` 설정 시 필수 | `gemini-3.1-flash-lite` / `gpt-4o-mini` / `claude-haiku-4-5` | +| `MINT_TARGET_LANG` | 목표 언어, 예: `en` 또는 `en,zh-TW,ja` | 시스템 로케일, 없으면 `en` | +| `MINT_VERBOSE` | `true`로 설정하면 상세 진단 출력 활성화 (`--verbose`와 동일) | `false` | + +--- + +## 🚩 CLI 플래그 + +| 플래그 | 축약형 | 설명 | +|------|------|------| +| `--target ` | `-t` | 목표 언어 (BCP-47 태그, 예: `ja`, `zh-TW`, `fr`). `MINT_TARGET_LANG`을 덮어씀. | +| `--source ` | `-s` | 소스 언어 (BCP-47 태그); 자동 감지를 건너뛰고 이 언어로부터 번역하도록 강제함. | +| `--verbose` | `-v` | 진단 정보와 토큰 사용량을 stderr로 출력. `MINT_VERBOSE=true`로도 활성화 가능. | +| `--version` | | 버전을 출력하고 종료. | + +--- + +## 📅 로드맵 + +- [x] 다중 LLM 프로바이더 지원 (Google Gemini, OpenAI, Anthropic 및 OpenAI 호환 엔드포인트) +- [x] `MINT_TARGET_LANG`을 통한 스마트 언어 감지 및 다국어 순환 +- [x] `--target` / `-t` 플래그를 통한 명시적 목표 언어 지정 +- [x] `--source` / `-s` 플래그를 통한 명시적 소스 언어 지정 +- [x] 스트리밍 출력 +- [x] GoReleaser 기반 멀티 플랫폼 바이너리 릴리스 (Linux / macOS / Windows) +- [ ] 배치 번역 모드 +- [ ] 용어집 / 사용자 지정 사전 지원 +- [ ] 출력 형식 옵션 (일반 텍스트, JSON, Markdown) +- [ ] 반복 번역 캐싱 + +--- + +## 📄 라이선스 + +Apache License 2.0 — 자세한 내용은 [LICENSE](https://github.com/min0625/mint/blob/main/LICENSE) 파일을 참고하세요. diff --git a/README.md b/README.md index 43658bb..7d643ad 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ cat document.txt | mint -t fr # translate a whole file ## ✨ Why Mint? - **Zero-config** — Single binary; API keys via env vars, no config file pollution -- **Multi-provider** — Google Gemini, OpenAI, Anthropic, or local Ollama / LM Studio +- **Multi-provider** — Google Gemini, OpenAI, Anthropic, or any OpenAI-compatible endpoint (Ollama, LM Studio, OpenRouter, Groq, DeepSeek, llama.cpp, …) - **Smart detection** — Auto-detects language on every call; language-neutral content (numbers, symbols) passes through unchanged - **Smart correction** — Same-language input? Auto-corrects grammar & spelling instead of translating - **Streaming** — Output streams in real-time, no waiting for long translations @@ -108,6 +108,29 @@ export MINT_MODEL_NAME=qwen2.5:7b # use any model loaded in Ollama export MINT_PROVIDER=openai export MINT_BASE_URL=http://localhost:1234 export MINT_MODEL_NAME=lmstudio-community/Qwen2.5-7B-Instruct-GGUF # use any model loaded in LM Studio + +# llama.cpp llama-server (no API key needed) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:8080 +export MINT_MODEL_NAME=qwen2.5:7b # match whatever model llama-server has loaded + +# OpenRouter (one key, hundreds of models — https://openrouter.ai/models) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://openrouter.ai/api +export MINT_API_KEY=sk-or-... +export MINT_MODEL_NAME=openai/gpt-4o-mini + +# Groq (fast inference, free tier) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.groq.com/openai +export MINT_API_KEY=gsk_... +export MINT_MODEL_NAME=llama-3.1-8b-instant + +# DeepSeek +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.deepseek.com +export MINT_API_KEY=sk-... +export MINT_MODEL_NAME=deepseek-chat ``` ### 2. Translate @@ -125,7 +148,6 @@ Use `--verbose` / `-v` (or `MINT_VERBOSE=true`) to print diagnostic info and tok ```bash mint -t ja -v "Good morning" # [mint] provider: google-genai -# [mint] model: gemini-3.1-flash-lite # [mint] single target — skipping language detection # [mint] target language: ja # おはようございます @@ -204,7 +226,7 @@ mint "こんにちは" # ja → en: Hello | `MINT_API_KEY` | API key; required when using the default endpoint; optional when `MINT_BASE_URL` is set (proxy handles auth) | — | | `MINT_BASE_URL` | Custom API base URL (domain only; each provider appends its own path); use with `openai` to target Ollama (`http://localhost:11434`), LM Studio (`http://localhost:1234`), or any other OpenAI-compatible endpoint | Provider default | | `MINT_MODEL_NAME` | Model to use; required when `MINT_BASE_URL` is set | `gemini-3.1-flash-lite` / `gpt-4o-mini` / `claude-haiku-4-5` | -| `MINT_TARGET_LANG` | Target language(s), e.g. `en` or `en,zh-TW,ja` | System locale | +| `MINT_TARGET_LANG` | Target language(s), e.g. `en` or `en,zh-TW,ja` | System locale, else `en` | | `MINT_VERBOSE` | Set to `true` to enable verbose diagnostic output (equivalent to `--verbose`) | `false` | --- @@ -222,7 +244,7 @@ mint "こんにちは" # ja → en: Hello ## 📅 Roadmap -- [x] Multi-LLM provider support (Google Gemini, OpenAI, Anthropic, local via Ollama / LM Studio) +- [x] Multi-LLM provider support (Google Gemini, OpenAI, Anthropic, or any OpenAI-compatible endpoint) - [x] Smart language detection and multi-language rotation via `MINT_TARGET_LANG` - [x] Explicit target language via `--target` / `-t` flag - [x] Explicit source language via `--source` / `-s` flag diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..856fbda --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,262 @@ +🌐 [其他语言](https://github.com/min0625/mint/blob/main/LANGUAGES.md) + +# 🌿 Mint + +> Minimalist AI Translation CLI — 极简,快速,直观。 + +[![GitHub Release](https://img.shields.io/github/v/release/min0625/mint?logo=github)](https://github.com/min0625/mint/releases) +[![PyPI](https://img.shields.io/pypi/v/mint-ai?logo=pypi&logoColor=white)](https://pypi.org/project/mint-ai/) +[![npm](https://img.shields.io/npm/v/mint-ai?logo=npm)](https://www.npmjs.com/package/mint-ai) +[![codecov](https://codecov.io/gh/min0625/mint/branch/main/graph/badge.svg)](https://codecov.io/gh/min0625/mint) + +Mint 是一款单一可执行文件、由 LLM 驱动的命令行翻译工具。只需配置两个环境变量,即可在命令行中翻译任意内容——文件、管道输出或直接输入的文本。内置语言检测、语法纠正、流式输出与多语言轮换功能。 + +```bash +export MINT_PROVIDER=google-genai +export MINT_API_KEY=your_key + +mint -t ja "Good morning" # おはようございます +echo "早安" | mint -t en # Good morning +cat document.txt | mint -t fr # 翻译整个文件 +``` + +--- + +## ✨ 为什么选择 Mint? + +- **零配置** — 单一可执行文件;API 密钥通过环境变量管理,不产生配置文件污染 +- **多提供商** — Google Gemini、OpenAI、Anthropic,或任何 OpenAI 兼容端点(Ollama、LM Studio、OpenRouter、Groq、DeepSeek、llama.cpp 等) +- **智能检测** — 每次调用自动检测语言;语言中性的内容(数字、符号)原样输出 +- **智能纠正** — 输入语言与目标语言相同?自动纠正语法与拼写,而非翻译 +- **流式输出** — 响应实时流式返回,翻译长文无需等待 +- **可组合** — 对 stdin/stdout 友好的设计;与 `grep`、`sed`、`xargs` 等工具无缝配合 +- **安全** — 通过 system/user 消息分离与每次请求的随机 nonce 分隔符,将不可信输入与模型指令隔离;翻译恶意内容也无法劫持 LLM 的行为 + +--- + +## 📋 安装 + +### 自动安装(推荐) + +**macOS / Linux** + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/min0625/mint/main/script/install.sh)" +``` + +自动检测操作系统与架构(Linux/macOS、x86_64/arm64),安装到 `~/.local/bin`。可通过 `MINT_INSTALL_DIR` 覆盖安装目录,或用 `MINT_VERSION=v1.0.0` 指定版本。 + +**Windows(PowerShell)** + +```powershell +irm https://raw.githubusercontent.com/min0625/mint/main/script/install.ps1 | iex +``` + +自动检测架构(x86_64/arm64),安装到 `$HOME\.local\bin`。可通过 `$env:MINT_INSTALL_DIR` 覆盖安装目录,或用 `$env:MINT_VERSION = 'v1.0.0'` 指定版本。 + +### Homebrew (macOS / Linux) + +```bash +brew install min0625/tap/mint-ai +``` + +### pipx + +```bash +pipx install mint-ai +``` + +### npm + +```bash +npm install -g mint-ai +``` + +### 手动下载 + +从 [GitHub Releases](https://github.com/min0625/mint/releases) 下载对应平台的预编译二进制文件,移动到 `PATH` 中的目录,然后验证: + +```bash +mint --version +``` + +--- + +## 🚀 快速上手 + +### 1. 配置提供商 + +```bash +# Google Gemini(提供免费额度 — https://aistudio.google.com/apikey) +export MINT_PROVIDER=google-genai +export MINT_API_KEY=your_gemini_api_key + +# OpenAI +export MINT_PROVIDER=openai +export MINT_API_KEY=sk-... + +# Anthropic +export MINT_PROVIDER=anthropic +export MINT_API_KEY=sk-ant-... + +# Ollama(无需 API 密钥) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:11434 +export MINT_MODEL_NAME=qwen2.5:7b # 替换为 Ollama 中已加载的任意模型 + +# LM Studio(无需 API 密钥) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:1234 +export MINT_MODEL_NAME=lmstudio-community/Qwen2.5-7B-Instruct-GGUF # 替换为 LM Studio 中已加载的任意模型 + +# llama.cpp 的 llama-server(无需 API 密钥) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:8080 +export MINT_MODEL_NAME=qwen2.5:7b # 替换为 llama-server 中已加载的模型 + +# OpenRouter(一个密钥,数百种模型 — https://openrouter.ai/models) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://openrouter.ai/api +export MINT_API_KEY=sk-or-... +export MINT_MODEL_NAME=openai/gpt-4o-mini + +# Groq(推理速度快,提供免费额度) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.groq.com/openai +export MINT_API_KEY=gsk_... +export MINT_MODEL_NAME=llama-3.1-8b-instant + +# DeepSeek +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.deepseek.com +export MINT_API_KEY=sk-... +export MINT_MODEL_NAME=deepseek-chat +``` + +### 2. 翻译 + +```bash +mint --target ja "Good morning" +mint -t zh-TW "Good morning" + +echo "The quick brown fox" | mint -t fr +cat document.txt | mint -t zh-TW +``` + +使用 `--verbose` / `-v`(或 `MINT_VERBOSE=true`)将诊断信息与 token 用量输出到 stderr: + +```bash +mint -t ja -v "Good morning" +# [mint] provider: google-genai +# [mint] single target — skipping language detection +# [mint] target language: ja +# おはようございます +# [mint] tokens: 113 in / 2 out +``` + +**典型 token 用量**(基于 `gemini-3.1-flash-lite` 实测): + +| 模式 | 输入 | API 调用次数 | 输入 tokens | 输出 tokens | +|------|------|-------------|------------|------------| +| 单一目标(`-t` 或单个 `MINT_TARGET_LANG`) | 短词/短句 | 1 | ~110–130 | ~1–15 | +| 单一目标 | 长文章(`testdata/sample.txt`) | 1 | ~465–470 | ~450–560 | +| 多目标轮换(逗号分隔 `MINT_TARGET_LANG`) | 短句 | 2 | ~250–260 | ~2–8 | +| 显式指定来源 `-s` + 轮换 | 短句 | 1 | ~105–120 | ~1–2 | + +> Token 数量随输入长度变化;输出 token 也因目标语言而异——日语与中文通常比英语产生更多 token。 + +**100 万 token 能翻译多少次?**(输入+输出合计,由上述实测用量推算): + +| 输入 | 每次约用 token | 每 100 万 token 可翻译次数 | +|------|---------------|--------------------------| +| 短词或短句 | 约 120 | 约 8,000 次 | +| 300 字文章 | 约 1,000 | 约 1,000 篇 | + +> 次数为输入与输出 token 合计。各提供商对输入和输出分别计价,且多数提供免费额度——具体费率请查阅提供商的定价页面。Google Gemini 在 [Google AI Studio](https://aistudio.google.com/apikey) 的免费额度无需绑定信用卡。 + +使用 `--source` / `-s` **强制指定来源语言**,可翻译那些在目标语言中同样合法的输入(跨语言同形词、罗马音文本): + +```bash +mint -s fr -t en "pain" # 法语 → bread(不加 -s 会被当作英语的 "pain") +mint -s ja -t en "konnichiwa" # 日语罗马音 → hello +``` + +### 3. 智能语言检测 + +**自动检测并翻译:** + +```bash +export MINT_TARGET_LANG=en + +mint "早安" # 检测到中文 → Good morning +``` + +**语法与拼写纠正** — 当输入语言与目标语言相同时,Mint 会纠正而非翻译: + +```bash +export MINT_TARGET_LANG=en + +mint "Good mooorning" # 检测到英语 → Good morning +mint "She don't know nothing" # 检测到英语 → She doesn't know anything +mint "i luv coding" # 检测到英语 → I love coding +``` + +**语言轮换** — 依次翻译为列表中的下一个语言,循环进行: + +```bash +# 两种语言 +export MINT_TARGET_LANG=en,zh-TW +mint "Hello" # en → zh-TW: 你好 +mint "你好" # zh-TW → en: Hello + +# 三种语言 +export MINT_TARGET_LANG=en,zh-TW,ja +mint "Hello" # en → zh-TW: 你好 +mint "你好" # zh-TW → ja: こんにちは +mint "こんにちは" # ja → en: Hello +``` + +--- + +## 🔑 环境变量 + +| 变量 | 说明 | 默认值 | +|------|------|--------| +| `MINT_PROVIDER` | `google-genai` \| `openai` \| `anthropic` | — (必填) | +| `MINT_API_KEY` | API 密钥;使用默认端点时必填;设置 `MINT_BASE_URL` 时可选(由代理处理鉴权) | — | +| `MINT_BASE_URL` | 自定义 API base URL(仅填域名,路径由各提供商自动附加);搭配 `openai` 可指向 Ollama(`http://localhost:11434`)、LM Studio(`http://localhost:1234`)或任何其他 OpenAI 兼容端点 | 提供商默认值 | +| `MINT_MODEL_NAME` | 使用的模型;设置 `MINT_BASE_URL` 时必填 | `gemini-3.1-flash-lite` / `gpt-4o-mini` / `claude-haiku-4-5` | +| `MINT_TARGET_LANG` | 目标语言,例如 `en` 或 `en,zh-TW,ja` | 系统区域设置,否则为 `en` | +| `MINT_VERBOSE` | 设为 `true` 可启用详细诊断输出(等同于 `--verbose`) | `false` | + +--- + +## 🚩 CLI 参数 + +| 参数 | 缩写 | 说明 | +|------|------|------| +| `--target ` | `-t` | 目标语言(BCP-47 标签,例如 `ja`、`zh-TW`、`fr`)。覆盖 `MINT_TARGET_LANG`。 | +| `--source ` | `-s` | 来源语言(BCP-47 标签);跳过自动检测,强制从此语言翻译。 | +| `--verbose` | `-v` | 将诊断信息与 token 用量输出到 stderr。也可通过 `MINT_VERBOSE=true` 启用。 | +| `--version` | | 显示版本并退出。 | + +--- + +## 📅 路线图 + +- [x] 多 LLM 提供商支持(Google Gemini、OpenAI、Anthropic,或任何 OpenAI 兼容端点) +- [x] 通过 `MINT_TARGET_LANG` 实现智能语言检测与多语言轮换 +- [x] 通过 `--target` / `-t` 参数明确指定目标语言 +- [x] 通过 `--source` / `-s` 参数明确指定来源语言 +- [x] 流式输出 +- [x] GoReleaser 多平台二进制发布(Linux / macOS / Windows) +- [ ] 批量翻译模式 +- [ ] 术语表 / 自定义词典支持 +- [ ] 输出格式选项(纯文本、JSON、Markdown) +- [ ] 翻译结果缓存 + +--- + +## 📄 许可证 + +Apache License 2.0 — 详见 [LICENSE](https://github.com/min0625/mint/blob/main/LICENSE) 文件。 diff --git a/README.zh-TW.md b/README.zh-TW.md index 136c69e..f076114 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -25,7 +25,7 @@ cat document.txt | mint -t fr # 翻譯整個檔案 ## ✨ 為什麼是 Mint? - **零設定** — 單一執行檔;API 金鑰透過環境變數管理,不污染設定檔 -- **多提供商** — Google Gemini、OpenAI、Anthropic,或本地 Ollama / LM Studio +- **多提供商** — Google Gemini、OpenAI、Anthropic,或任何 OpenAI 相容端點(Ollama、LM Studio、OpenRouter、Groq、DeepSeek、llama.cpp 等) - **智慧偵測** — 每次呼叫皆自動偵測語言;語言中性的內容(數字、符號)原樣輸出 - **智慧修正** — 輸入語言與目標語言相同?自動修正語法與拼字,而非翻譯 - **串流輸出** — 即時串流回應,翻譯長文不需等待 @@ -108,6 +108,29 @@ export MINT_MODEL_NAME=qwen2.5:7b # 替換為 Ollama 中已載入的任意模 export MINT_PROVIDER=openai export MINT_BASE_URL=http://localhost:1234 export MINT_MODEL_NAME=lmstudio-community/Qwen2.5-7B-Instruct-GGUF # 替換為 LM Studio 中已載入的任意模型 + +# llama.cpp 的 llama-server(無需 API 金鑰) +export MINT_PROVIDER=openai +export MINT_BASE_URL=http://localhost:8080 +export MINT_MODEL_NAME=qwen2.5:7b # 替換為 llama-server 中已載入的模型 + +# OpenRouter(一把金鑰、數百種模型 — https://openrouter.ai/models) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://openrouter.ai/api +export MINT_API_KEY=sk-or-... +export MINT_MODEL_NAME=openai/gpt-4o-mini + +# Groq(推論速度快,有免費層級) +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.groq.com/openai +export MINT_API_KEY=gsk_... +export MINT_MODEL_NAME=llama-3.1-8b-instant + +# DeepSeek +export MINT_PROVIDER=openai +export MINT_BASE_URL=https://api.deepseek.com +export MINT_API_KEY=sk-... +export MINT_MODEL_NAME=deepseek-chat ``` ### 2. 翻譯 @@ -125,7 +148,6 @@ cat document.txt | mint -t zh-TW ```bash mint -t ja -v "Good morning" # [mint] provider: google-genai -# [mint] model: gemini-3.1-flash-lite # [mint] single target — skipping language detection # [mint] target language: ja # おはようございます @@ -204,7 +226,7 @@ mint "こんにちは" # ja → en: Hello | `MINT_API_KEY` | API 金鑰;使用預設 endpoint 時必填;設定 `MINT_BASE_URL` 時選填(由代理處理認證) | — | | `MINT_BASE_URL` | 自訂 API base URL(僅填 domain,各提供商自行附加路徑);搭配 `openai` 可指向 Ollama(`http://localhost:11434`)、LM Studio(`http://localhost:1234`)或任何 OpenAI 相容端點 | 提供商預設 | | `MINT_MODEL_NAME` | 使用的模型;設定 `MINT_BASE_URL` 時必填 | `gemini-3.1-flash-lite` / `gpt-4o-mini` / `claude-haiku-4-5` | -| `MINT_TARGET_LANG` | 目標語言,例如 `en` 或 `en,zh-TW,ja` | 系統區域設定 | +| `MINT_TARGET_LANG` | 目標語言,例如 `en` 或 `en,zh-TW,ja` | 系統區域設定,否則 `en` | | `MINT_VERBOSE` | 設為 `true` 可啟用詳細診斷輸出(等同於 `--verbose`) | `false` | --- @@ -222,7 +244,7 @@ mint "こんにちは" # ja → en: Hello ## 📅 發展藍圖 -- [x] 多 LLM 提供商支援(Google Gemini、OpenAI、Anthropic,本地透過 Ollama / LM Studio) +- [x] 多 LLM 提供商支援(Google Gemini、OpenAI、Anthropic,或任何 OpenAI 相容端點) - [x] 透過 `MINT_TARGET_LANG` 實現智慧語言偵測與多語言輪換 - [x] 透過 `--target` / `-t` 旗標明確指定目標語言 - [x] 透過 `--source` / `-s` 旗標明確指定來源語言 diff --git a/docs/manual-testing.md b/docs/manual-testing.md index cd16146..818fe77 100644 --- a/docs/manual-testing.md +++ b/docs/manual-testing.md @@ -210,10 +210,20 @@ mint -s en "Hello" # 你好 (en matched → next: zh-TW; -v: expl `-s` accepts only a single language tag; like `-t`, a comma in the value is truncated to the first tag. `--source` is the long form of `-s`. There is no `MINT_SOURCE_LANG` env var — a source is per-input, not a persistent preference. +> **Model note:** whether the homograph is actually translated depends on model quality, not +> on the CLI — `-v` confirms the anchor is sent (`source language: fr`) either way. Observed +> (2026-07): `gemini-3.1-flash-lite`, `claude-haiku-4-5`, and `gemma-4-e4b` return `bread`; +> `gpt-4o-mini` and `llama3.1:8b` keep `pain` unchanged. Treat a wrong result here as a +> model limitation, not a `-s` regression, unless the verbose anchor line is missing. + ## 13. Error cases All errors go to stderr; the process exits with code 1. +Config validation runs before input validation: if `MINT_PROVIDER` is unset, empty input +reports the provider error, not `no input text provided`. The cases below assume a valid +provider config is already in place (per the header of this file). + ```sh # Empty or whitespace-only input mint -t zh-TW "" # Error: no input text provided