# Localization Release QA (`h_murdock/localization-release-qa`) Actor

Check supplied locale JSON for missing keys, empty text, type changes, interpolation errors and declared i18next plural categories. Download translator CSV, JSON and HTML.

- **URL**: https://apify.com/h\_murdock/localization-release-qa.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.15 / completed report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Localization Release QA do?

Catch structural defects in a translation delivery before a release. Supply a base locale and target locale JSON resources, choose a syntax policy, and receive a translator review queue with exact paths, evidence, and suggested human actions. The Actor runs entirely on the supplied data; it does not fetch a website, upload strings to a translation service, or generate translations.

### Why use Localization Release QA?

Use it after a translator returns a resource bundle, before merging localization changes, or as a release step in an Apify, n8n, or Make workflow. A structurally valid JSON file can still omit a key, rename an interpolation variable, or change an array into an object. This report makes those differences explicit.

### How to use Localization Release QA

1. Open the Input tab and paste your base resource and target locales.
2. Choose `plain` or `i18next-v4`, then declare any plural families and exclusions.
3. Run the Actor and review the summary before handing the issue CSV to your translator.

Use this input example:

```json
{
  "baseLocale": "en",
  "base": {
    "greeting": "Hello {{name}}",
    "cart_one": "One item",
    "cart_other": "{{count}} items"
  },
  "locales": [
    {
      "locale": "de",
      "translations": {
        "greeting": "Hallo {{vorname}}",
        "cart_one": "Ein Artikel",
        "cart_other": "{{count}} Artikel"
      }
    }
  ],
  "syntax": "i18next-v4",
  "pluralFamilies": ["/cart"]
}
```

This example reports one missing variable, `name`, and one extra variable, `vorname`. It does not require `{{count}}` in the singular string. For each run, download the full JSON report, translator CSV, or standalone HTML file from the output links.

Supply exactly one `base` object or `baseJson` string containing pasted JSON. For each target, supply exactly one `translations` object or `translationsJson` pasted string. Pasted text is checked for duplicate JSON member names, including escaped-equivalent names. Once another JSON parser has already discarded duplicate object keys, those lost duplicates cannot be recovered; use the text fields when duplicate detection matters.

### Input checks and policies

- **Missing and extra keys:** compared recursively. A wholly missing or extra subtree creates one finding at its root. Strings in present extra subtrees are also inspected for unsupported syntax.
- **Value kinds:** strings, numbers, booleans, null, arrays, and objects remain distinct. Non-text leaves and empty containers receive review warnings. String identifiers such as `001` remain strings.
- **Empty text:** empty or whitespace-only strings are errors by default. Set `treatEmptyAsMissing` to `false` to make them review warnings; they still appear in the report.
- **Interpolation:** `i18next-v4` supports simple `{{name}}`, data-model paths such as `{{author.name}}`, and unescaped `{{- name}}`. Variable names and the set of escape modes must match. Word order and repeated occurrences are not compared.
- **Plain mode:** keys such as `item_one` have no plural meaning. Interpolation-like text creates an unsupported-syntax finding. Undeclared suffix keys also remain ordinary keys in i18next mode.
- **Exact ignores:** `ignorePaths` contains at most 200 exact JSON Pointers. The named node and its descendants are excluded, and every ignored path must exist in at least one locale. There are no regular expressions or wildcard patterns. A literal `*` is simply a key character.

Paths use JSON Pointer rather than dot notation. `/checkout.greeting` is a literal dotted key, `/checkout/greeting` is a nested key, `/items/0` is the first array element, and `~1` / `~0` escape slash / tilde characters in member names. Array-versus-object distinctions are retained in the evidence types. The empty pointer denotes the root.

### Cardinal plural families

Declare exact family stems in `pluralFamilies`, for example `/cart/items` for keys `items_one` and `items_other` under `cart`. The base must contain an object parent and at least one recognized category key. At most 500 families are allowed. A declared family or category cannot be ignored.

The Actor uses each locale's `Intl.PluralRules` cardinal categories. Arabic and Japanese therefore do not need the same suffix keys as English. It checks required categories in the base as well as targets. A `_zero` special form is allowed even when not required by that locale. Extra nonzero categories receive review warnings. Plural category values must be strings.

For placeholder comparison, a target category uses the same source category when present, otherwise source `_other`. This is an explicit structural reference policy, not a claim that every grammatical form needs identical wording. Legitimate language-specific placeholder differences need human review. The application still needs to pass the i18next runtime `count` option; the Actor cannot inspect calls to `t()` and does not require the string itself to contain a count placeholder. The report records Node and ICU versions and the resolved category lists.

### Unsupported syntax and scope

ICU messages, sprintf placeholders, i18next nesting, format expressions, ordinal/interval plurals, custom delimiters, runtime interpolation settings, and framework plugins are not parsed. Common signatures and unresolved braces produce explicit findings. Detection is conservative and may flag literal braces or delimiter-like prose. Arbitrary custom syntax cannot be discovered reliably from text alone: declare known features in `unsupportedFeatures` using `icu`, `sprintf`, `nesting`, `custom-delimiters`, `formatting`, `ordinal`, or `interval`. Every declaration forces review. No report with an unsupported finding receives `passed` status.

The supported simple variable grammar uses Unicode letters/numbers, underscore, and dollar sign in dot-separated segments. Other variable expressions are flagged. This Actor does not assess translation meaning, fluency, terminology, language identity, codebase unused keys, runtime fallback behavior, markup correctness, visual layout, accessibility, or regulatory compliance. It performs no automatic edits.

### Output and automation

The dataset contains exactly one complete report with `summary`, `policy`, `localeRules`, and `rows`. Each issue has a stable ID, severity, code, locale, JSON Pointer, source reference, evidence types, evidence excerpts, reason, and human action. Stable IDs depend on engine version, locale, path, issue code, and variable identity; unrelated new findings do not renumber existing issues.

`summary.status` is `failed` when errors exist, `review-required` for warnings only, and `passed` when no findings remain within the configured scope. In a workflow, wait for run completion, read the first dataset item, and gate the release on your chosen status. A completed Actor run with `failed` QA status is still a successfully delivered report.

The `OUTPUT` JSON record mirrors the report. `translator-issues.csv` provides one issue per row with a UTF-8 BOM, quoted fields, and spreadsheet-formula neutralization. `report.html` is escaped, standalone, and script-free; use the download link and open the file locally. Evidence uses JSON-encoded text excerpts, capped at 600 UTF-16 code units plus an ellipsis; `evidenceTruncated` marks clipping. Containers show type and size. Full original values remain in your supplied input, which the Actor does not modify. Logs contain status counts rather than customer translation values.

A shortened issue example is:

```json
{
  "summary": { "status": "failed", "issues": 2, "errors": 2 },
  "rows": [
    {
      "code": "INTERPOLATION_MISSING",
      "locale": "de",
      "path": "/greeting",
      "reason": "Missing interpolation variable: name.",
      "action": "Restore the exact source variable name, keeping its escape mode."
    }
  ]
}
```

| Output field | How to use it |
| --- | --- |
| `summary.status` | Gate a release on your chosen acceptance policy. |
| `rows[].id` | Track the same finding across repeated deliveries. |
| `rows[].locale`, `path` | Locate the resource value needing attention. |
| `rows[].referencePath` | Find the source form used for comparison. |
| `rows[].baseEvidence`, `targetEvidence` | Review preserved excerpts and types. |
| `rows[].action` | Assign a concrete review step to a translator or developer. |

### Limits, price, and recovery

A run accepts 2–20 locales including the base, 10,000 combined leaves, and translation depth 20. Empty objects/arrays count as leaves. The complete input, including pasted-text escaping and policy metadata, must be at most 2,000,000 UTF-8 bytes. This conservative whole-input limit is slightly tighter than measuring raw translation text alone. No sampling occurs. Reports must remain below 20,001 findings, 8 MB of JSON, and 9 MB per downloadable export. If an output would exceed a limit, split the locales or resource files; no partial report event is charged.

The listed price is **$0.15 per completed report**, including platform usage. Invalid input or insufficient run budget causes no report event. A completed audit containing errors is billable. Each new run is independently billable. Paid configuration supports only the `report-completed` event; positive startup, default dataset-item, or other events are rejected. Promotional prices below $0.15 are accepted.

The dataset JSON is the primary deliverable. If an export write is interrupted after publication and charging, the dataset remains available; resurrecting that same run regenerates downloads without a second report event. Recovery checks the input fingerprint and recomputes the supplied-data report to reject modified evidence, engine changes, or runtime changes. An interrupted charge after the dataset write is completed once on recovery. A charged run whose dataset item is missing is refused for inspection. Cloud storage and billing remain platform operations; this behavior does not promise an atomic multi-file transaction.

No external account, proxy, API key, browser session, or translation-service subscription is required. Keep unreleased translations in your own authorized Apify storage and apply your normal retention and sharing settings.

### Tips and support

Save the same explicit policy in an Apify Task to reuse it after each translation delivery. Apify API access and integrations can connect the report to an existing workflow; the Actor itself does not watch files or maintain translation history. Split resource namespaces or locale groups when a bundle approaches the documented limits. Use the Actor’s Issues tab for reproducible bug reports using synthetic strings instead of confidential customer text.

The supported syntax follows the official [i18next interpolation documentation](https://www.i18next.com/translation-function/interpolation) and [cardinal plural documentation](https://www.i18next.com/translation-function/plurals), with the narrower parser boundaries described above.

# Actor input Schema

## `baseLocale` (type: `string`):

BCP 47 locale supported by Node Intl. Locale identifiers must be unique after canonicalization.

## `base` (type: `object`):

Base resource. Use either this object or baseJson. Preserve literal dots, arrays, leading-zero strings and original values.

## `baseJson` (type: `string`):

Alternative to base. Pasted JSON is checked for duplicate member names before parsing. Omit base when using this.

## `locales` (type: `array`):

1–19 entries: locale plus exactly one translations object or translationsJson pasted JSON text. Canonical locale tags must be unique.

## `syntax` (type: `string`):

plain treats suffix keys as ordinary keys and flags interpolation. i18next-v4 supports default simple {{name}}, {{author.name}} and {{- name}}; declared cardinal families only.

## `pluralFamilies` (type: `array`):

Exact JSON Pointer stems, e.g. /cart/items for items\_one/items\_other. Up to 500. Base parent must be an object with a known category. No patterns. Plain mode requires an empty list.

## `ignorePaths` (type: `array`):

Up to 200 exact JSON Pointers. Each path must exist in some locale; the named node and descendants are excluded. No wildcards or regex. Cannot ignore declared plural families/categories.

## `treatEmptyAsMissing` (type: `boolean`):

Whitespace-only text is always reported. True makes it an error; false makes it a review warning.

## `unsupportedFeatures` (type: `array`):

Declare syntax used by your app that this engine cannot parse. Each declaration forces review; automatic detection alone cannot discover arbitrary custom delimiters. Allowed values: icu, sprintf, nesting, custom-delimiters, formatting, ordinal, interval. Other values are rejected.

## Actor input object example

```json
{
  "baseLocale": "en",
  "base": {
    "hello": "Hello {{name}}"
  },
  "locales": [
    {
      "locale": "de",
      "translations": {
        "hello": "Hallo {{name}}"
      }
    }
  ],
  "syntax": "i18next-v4",
  "pluralFamilies": [],
  "ignorePaths": [],
  "treatEmptyAsMissing": true,
  "unsupportedFeatures": []
}
```

# Actor output Schema

## `report` (type: `string`):

One item including summary, policy, locale rules and rows. Primary deliverable.

## `json` (type: `string`):

Structured complete report.

## `csv` (type: `string`):

Formula-safe issue rows with evidence and actions.

## `html` (type: `string`):

Escaped standalone report; download and open locally.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "baseLocale": "en",
    "base": {
        "hello": "Hello {{name}}"
    },
    "locales": [
        {
            "locale": "de",
            "translations": {
                "hello": "Hallo {{name}}"
            }
        }
    ],
    "syntax": "i18next-v4"
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/localization-release-qa").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "baseLocale": "en",
    "base": { "hello": "Hello {{name}}" },
    "locales": [{
            "locale": "de",
            "translations": { "hello": "Hallo {{name}}" },
        }],
    "syntax": "i18next-v4",
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/localization-release-qa").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "baseLocale": "en",
  "base": {
    "hello": "Hello {{name}}"
  },
  "locales": [
    {
      "locale": "de",
      "translations": {
        "hello": "Hallo {{name}}"
      }
    }
  ],
  "syntax": "i18next-v4"
}' |
apify call h_murdock/localization-release-qa --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/localization-release-qa"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/9OprOxgtXtt60JNMM/builds/6olcZ80dX5AjTHCFE/openapi.json
