Runtime i18n correctness audit — catches the non-Latin data corruption bugs that gettext-style audits miss. Scans every wp_json_encode / json_encode call in paths that store post_meta, options, REST output, or LLM/external-API request bodies; flags any missing JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES flag. Also verifies Content-Type charset=utf-8 on REST responses. Use when the user says "i18n runtime", "Unicode encoding", "non-Latin characters", "Turkish broken", "CJK garbage", "data ...
Scanned 9/9/2026
Install to Claude Code
npx -y skills add adityaarsharma/orbit --skill orbit-i18n-runtime --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Orbit I18n Runtime?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adityaarsharma-orbit-i18n-runtime)More formats (shields.io, HTML) on the badges page.
---
name: orbit-i18n-runtime
description: Runtime i18n correctness audit — catches the non-Latin data corruption bugs that gettext-style audits miss. Scans every wp_json_encode / json_encode call in paths that store post_meta, options, REST output, or LLM/external-API request bodies; flags any missing JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES flag. Also verifies Content-Type charset=utf-8 on REST responses. Use when the user says "i18n runtime", "Unicode encoding", "non-Latin characters", "Turkish broken", "CJK garbage", "data corruption on save", or before any release that ships to non-English locales.
---
# 🪐 orbit-i18n-runtime — Runtime data i18n correctness
`orbit-i18n` is gettext-only — it checks UI strings get wrapped in `__()`. This skill checks the **other half of i18n**: when the plugin writes Turkish, CJK, Arabic, Hindi, Cyrillic, or any non-Latin data into post_meta / options / REST responses / outgoing API bodies, does it survive the round-trip without corruption?
**Why this skill exists:** example-plugin shipped v1.0.x storing non-Latin content as `\uXXXX` escape sequences via default `wp_json_encode()`. WordPress's sanitization filters dropped the backslashes; "Yatırım" became visible garbage "Yu0131lu0131" in the database. The fix required patching 4 modules + 4 LLM provider classes + a 1-shot migration to repair existing data. `orbit-i18n` did not catch it because it was looking at `__()` wrapping, not JSON encoding flags. Plugin-agnostic — applies to any WP plugin that stores or transmits user-supplied content.
---
## What this skill checks
### 1. `wp_json_encode()` / `json_encode()` in storage paths
Scan every call site. If the encoded value flows into:
- `update_post_meta()`, `add_post_meta()`, `update_metadata()`, `update_user_meta()`, `update_term_meta()`
- `update_option()`, `update_site_option()`, `set_transient()`
- A REST response body (`rest_ensure_response`, `WP_REST_Response`)
- A `wp_remote_post()` / `wp_remote_request()` body posted to any external HTTP endpoint
Then it MUST include both flags:
```php
wp_json_encode( $data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
```
#### ❌ Bad
```php
update_post_meta( $post_id, '_my_summary', wp_json_encode( $summary ) );
// "Yatırım" → "Yatırım" in DB → backslash-stripped → "Yu0131ru0131m" garbage
```
#### ✅ Good
```php
update_post_meta(
$post_id,
'_my_summary',
wp_json_encode( $summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
);
```
### 2. LLM / external-API outgoing bodies
Every `wp_remote_post( $url, [ 'body' => ... ] )` where the body is JSON-encoded must use the flags. Without them:
- Wire bytes inflate ~6× for CJK / Arabic / Hindi content
- Token billing inflates proportionally
- Some upstream providers reject `\uXXXX` sequences in specific fields
#### Detection grep
```bash
# Files using wp_json_encode without UNESCAPED_UNICODE on the same line or next line
grep -rn 'wp_json_encode\|json_encode' --include='*.php' . \
| grep -v 'JSON_UNESCAPED_UNICODE' \
| grep -v 'tests/'
```
Every hit is a candidate. Inspect each: is the encoded value persisted or sent externally? If yes → flag.
### 3. REST response `Content-Type` charset
Custom REST routes that ship translated content MUST set `charset=utf-8` explicitly. Default WP responses do this, but custom output paths via `wp_die()`, `echo`, or direct `header()` calls often forget.
#### ❌ Bad
```php
header( 'Content-Type: text/markdown' );
echo $markdown; // browser guesses encoding → mangles non-Latin
```
#### ✅ Good
```php
header( 'Content-Type: text/markdown; charset=utf-8' );
echo $markdown;
```
#### Detection grep
```bash
grep -rn "header(.*Content-Type" --include='*.php' . \
| grep -v 'charset=utf-8' \
| grep -v 'tests/'
```
### 4. Round-trip safety: writer/reader flag parity
If the writer uses `JSON_UNESCAPED_UNICODE`, the reader's `json_decode()` works on either format — no change needed. BUT: if you mix writers (some flagged, some not) on the same meta key, you get garbled data depending on which writer last wrote.
**Audit pattern:**
1. Find every plugin-namespaced meta key written via JSON encoding.
2. List every call site for that key.
3. All writers must use the same flag set.
### 5. Migration / repair routine for legacy data
If the plugin previously shipped without the flags, an upgrade routine must re-encode existing rows:
```php
add_action( 'plugins_loaded', function() {
if ( get_option( 'myplugin_unicode_meta_migrated_v_X_Y_Z' ) ) {
return;
}
// Query postmeta for rows containing \uXXXX patterns, json_decode + wp_json_encode
// with new flags, $wpdb->update. Batch-cap at 2000 rows per request.
// Set the option flag when row count < 2000.
} );
```
The skill flags the ABSENCE of this routine when the plugin's version history shows a pre-flag → post-flag transition.
### 6. Magic-quotes / `wp_unslash` interaction
After reading `$_POST` content destined for JSON encoding, `wp_unslash()` is mandatory BEFORE encoding. Without it, backslashes from slash-escaping survive into the encoded payload and combine with `\uXXXX` sequences to produce double-corrupted output.
```php
// ❌
$data = $_POST['data'];
update_post_meta( $id, '_my_data', wp_json_encode( $data ) );
// ✅
$data = wp_unslash( $_POST['data'] );
update_post_meta(
$id, '_my_data',
wp_json_encode( $data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
);
```
---
## Report format
```markdown
# i18n Runtime Audit — [Plugin]
## Summary
- wp_json_encode calls missing UNESCAPED_UNICODE: <N>
- Persisted (post_meta/options): <X> ← Critical
- External API bodies: <Y> ← High
- REST responses: <Z> ← High
- Read-only / logs: <W> ← Low
- REST routes missing charset=utf-8: <N>
- Mixed writer/reader flag sets: <N>
- Missing legacy-repair migration: <Y/N>
- $_POST → wp_json_encode without wp_unslash: <N>
## Critical (block release)
### Persisted non-Latin corruption risk
**File:** includes/class-foo.php:305
**Code:**
```php
update_post_meta( $post_id, '_foo_data', wp_json_encode( $data ) );
```
**Fix:** add `JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES` to the second arg.
**Why:** stored \uXXXX → WP filter strips backslashes → "Yatırım" becomes "Yu0131ru0131m"
[Continue for every finding]
```
---
## Plug-in agnostic
This skill never references example-plugin or any specific plugin. It scans for **patterns**, not bugs. If a future plugin (example-plugin, example-plugin, example-plugin, third-party) ships any of the call shapes above without the flags, this skill flags it.
---
## Cross-references
- **`orbit-i18n`** — covers gettext wrapping. Run both. Different surfaces.
- **`orbit-i18n-js-parity`** — JS↔PHP label mismatches (different bug class).
- **`orbit-compat-polylang` / `orbit-compat-wpml`** — language-aware custom endpoints (different bug class — see §7 in each).
- **§10.6 in orbit-code-reviewer** — `$_POST` reads without `wp_unslash()` (related, broader scope).
- **CTO brain `orbit/00-cto`** — promote new corruption patterns here when discovered.
---
## Severity
- Persisted-meta corruption: **Critical** (silent data loss; multi-version migration to fix)
- External API body without flag: **High** (token inflation + potential upstream rejection)
- REST response without charset: **High** (consumer-side display corruption)
- Mixed flag sets across writers: **High** (last-writer-wins data corruption)
- Missing legacy-repair migration: **High** (existing installs stay broken after fix)
- `$_POST` without `wp_unslash` before encode: **High**
Block release on any Critical or High that isn't an explicit accepted risk in `orbit/00-cto`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!