Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

msgai

msgai is an AI-powered CLI for translating gettext .po files. It finds untranslated entries, sends them to an LLM, and writes the translated strings back into the same file.

🤖 Project Purpose

msgai is built for teams that already use gettext and want a simple way to translate missing strings without building a separate localization workflow.

Main features:

  • 📝 Works directly with gettext .po files
  • 🤖 Translates only untranslated entries using AI
  • 🧠 Uses OpenAI gpt-5.4 by default for translation
  • 🏷️ Respects gettext context (msgctxt) when translating entries
  • 🔁 Supports singular and plural translations
  • ⚠️ Skips fuzzy entries by default
  • 🪪 Marks every AI translation with a # ai-translated translator comment
  • 🧭 Can infer source language or use --source-lang
  • 💻 Runs as a small CLI that updates files in place

⚙️ How It Works

  1. Read the .po file and parse its entries.
  2. Find entries with empty or missing translations.
  3. Send those strings to OpenAI gpt-5.4 for translation while preserving gettext context such as msgctxt.
  4. Write the translated values back into the same .po file.

The translation API uses OpenAI json_schema structured outputs. Only models that support json_schema structured outputs are valid for msgai.

Any OpenAI model that supports json_schema structured outputs can be used via the --model flag.

By default, entries marked as fuzzy are skipped. If you use --include-fuzzy, msgai will translate those entries too and remove the fuzzy flag after applying the result.

Every entry that msgai translates gets a # ai-translated translator comment so you can tell AI translations apart from human ones. Existing translator comments are preserved. Use --add-fuzzy to additionally mark fresh translations with the gettext fuzzy flag — useful when you want a human to review every AI translation before it ships.

📦 Install

Install the CLI globally:

npm install -g msgai-cli

Set your OpenAI API key before running translations:

export OPENAI_API_KEY=your_api_key_here

You can also pass the key directly:

msgai messages.po --api-key sk-...

OPENAI_API_KEY can be loaded from your environment or from a .env file in the current directory.

💻 CLI Usage

Usage:

msgai <file.po> [--dry-run] [--api-key KEY] [--source-lang LANG] [--model MODEL] [--include-fuzzy] [--add-fuzzy] [--fold-length N] [--context TEXT] [--config PATH] [--debug]

Options:

  • --dry-run: list untranslated msgid values only, with no API calls and no file changes
  • --include-fuzzy: include fuzzy entries for translation and clear their fuzzy flag after translation
  • --add-fuzzy: mark every newly translated entry with the gettext fuzzy flag (so a human reviews it before it ships). Independent of --include-fuzzy
  • --source-lang LANG: set the source language of msgid strings as an ISO 639-1 code such as en or uk
  • --model MODEL: set the OpenAI model used for translation; default is gpt-5.4. Only models with json_schema structured outputs are supported.
  • --api-key KEY: pass the OpenAI API key directly instead of using OPENAI_API_KEY
  • --fold-length N: set PO line fold length when writing files. Use 0 to disable folding and minimize formatting-only diffs. Default: 0
  • --context TEXT: additional instructions for the translation model in English, appended to the system prompt (e.g. "use formal tone", "don't translate currency names")
  • --config PATH: path to a YAML config file (default: msgai.config.yml in current directory)
  • --debug: print debug logs for batch preparation, OpenAI request retries, request payloads, and raw response validation
  • --help: print command usage

You can also enable the same debug logging with the environment variable DEBUG=1:

DEBUG=1 msgai messages.po

Configuration File

msgai supports an optional msgai.config.yml config file in the project directory. If found, its values are used as defaults. CLI arguments always override config file values.

Use --config PATH to specify a custom config file location. If --config is not provided, msgai looks for msgai.config.yml in the current working directory.

Example msgai.config.yml:

source-lang: enmodel: gpt-5.4include-fuzzy: falseadd-fuzzy: falsefold-length: 80context: "use formal tone"debug: false

Both kebab-case and camelCase keys are accepted.

api-key and dry-run are not allowed in the config file. API keys should be set via --api-key flag or OPENAI_API_KEY environment variable for security reasons. dry-run is a runtime-only option that must be passed as a CLI flag.

If no API key is provided for a non-dry run, the CLI exits with code 1 and prints an error message.

On API failures such as rate limits, quota issues, or server errors, the CLI exits with code 1 and shows a status-specific message. Validation errors for protected fields such as msgid, msgid_plural, or msgctxt now tell you whether a retry is reasonable and when to rerun with --debug or DEBUG=1 to inspect the request/response flow. For API error details, see OpenAI API error codes.

🧪 Development

Requirements:

  • Node.js 20+
  • npm 10+

Install dependencies:

npm install

Useful scripts:

  • npm run build: compile TypeScript to dist/
  • npm test: build the project and run Jest tests
  • npm run test:integration: run integration tests
  • npm run test:watch: run tests in watch mode
  • npm run lint: run ESLint
  • npm run lint:format: check formatting with Prettier
  • npm run format: format the repository with Prettier
  • npm run release:dry-run: preview the commit-and-tag-version release without writing files
  • npm run release: run release checks, update CHANGELOG.md, bump the npm version, create a release commit, and create a local tag

This repo follows Conventional Commits for commit messages.

Release Flow

Maintainer releases are local-first and use commit-and-tag-version. The release command does not publish to npm or push tags for you.

Preview the next release:

npm run release:dry-run

Create the release locally:

npm run release

This command:

  • runs build, unit tests, integration tests, lint, and formatting checks through the prerelease lifecycle hook
  • lets commit-and-tag-version infer major, minor, or patch from Conventional Commits since the latest v* tag
  • updates CHANGELOG.md
  • creates chore(release): X.Y.Z
  • creates a local annotated tag vX.Y.Z

For reliable version bumps and changelog entries, keep commits in Conventional Commit format.

If you need to override the inferred bump manually:

npm run release -- --release-as minor

After the local release is created:

git push --follow-tags
npm publish

About

AI-powered CLI for translating gettext

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages