audit-i18n — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited audit-i18n (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Translations that read like machine output erode user trust — especially in the user's own language. A Japanese user reading stiff, over-literal English translated to Japanese can feel it immediately. This skill finds every unnatural, jargon-heavy, or technically-worded string and rewrites it to sound like a real person in that locale.
The goal: every string should sound like it was written by a native speaker who understands the user, not translated from English by a developer.
These apply to every string in every locale:
| Bad | Better | Why |
|---|---|---|
| "Invalid email format" | "That email doesn't look right — check for typos" | Guides, doesn't blame |
| "An error occurred" | "Something went wrong. Try again, or contact us if it keeps happening" | Actionable |
| "Please enter a valid value" | "This field is required" or "Enter a number between 1 and 100" | Specific |
| "Authentication failed" | "Wrong email or password" | Plain language |
| "Submit" | "Save", "Send message", "Create account" | Says what actually happens |
| "OK" | "Got it", "Done", or the specific action | Less dismissive |
| "User" | "you", "your account" | Conversational |
| "The operation was successful" | "Done!" or "Saved" | Natural |
| "Do you want to proceed?" | "Are you sure? This can't be undone." | Honest and specific |
package.json → i18next, react-i18next, next-intl, vue-i18n, @angular/localize,
i18n-js, rosetta, lingui, typesafe-i18n
src/i18n/ → translation files
locales/ → JSON/YAML translation files
messages/ → next-intl message files
public/locales → common path for react-i18nextIdentify:
en, ja, fr, de, zh, etc.)Fetch library docs:
CallMcpTool(server: "plugin-context7-plugin-context7", toolName: "resolve-library-id", arguments: {
"libraryName": "react-i18next"
})Look for user-facing text that bypasses the i18n system entirely.
Scan for:
<p>Welcome back</p>, <button>Save</button> Hello ${name} `toast.error("Something failed")placeholder="Enter your email"aria-label="Close dialog"alt text on meaningful imagesUse shell to scan (the codebase search tools may be slow on large repos):
grep -rn 'placeholder="' src/ --include="*.tsx" --include="*.jsx" | head -30
grep -rn '"en":' src/ --include="*.ts" --include="*.tsx" | head -20Build a list: file path, line number, string content, whether it has an i18n key already.
For each locale, check that all keys from the base locale (usually en) exist:
# Example: compare en.json vs ja.json key counts
node -e "
const en = require('./public/locales/en/common.json');
const ja = require('./public/locales/ja/common.json');
const missing = Object.keys(en).filter(k => !ja[k]);
console.log('Missing in ja:', missing);
"Or for next-intl format:
node -e "
const en = require('./messages/en.json');
const ja = require('./messages/ja.json');
function findMissing(base, target, path='') {
for (const k of Object.keys(base)) {
const kp = path ? path+'.'+k : k;
if (!(k in target)) console.log('MISSING:', kp);
else if (typeof base[k] === 'object') findMissing(base[k], target[k], kp);
}
}
findMissing(en, ja);
"This is the most important phase. For each locale, review every string against the natural-language principles at the top of this skill.
Error messages — must guide, not blame, and tell the user what to do:
// Before
"errors.email_invalid": "Invalid email address"
// After
"errors.email_invalid": "That doesn't look like a valid email — check for typos and try again"Action buttons — must describe the outcome, not just label the input:
// Before
"buttons.submit": "Submit"
"buttons.confirm": "OK"
// After
"buttons.submit": "Save changes" // or whatever the action actually does
"buttons.confirm": "Got it" // or the specific action: "Delete account", "Send message"Empty states — should explain and invite, not just state the absence:
// Before
"empty.projects": "No projects"
// After
"empty.projects": "You haven't created any projects yet. Start one to get going."Success messages — should feel warm and confirm the action clearly:
// Before
"success.saved": "Data saved successfully"
// After
"success.saved": "Saved!" // or "Your changes are saved"Loading states — should be human:
// Before
"loading.data": "Loading data..."
// After
"loading.data": "Getting things ready…" // or just omit "Loading" entirely| Locale | Tone notes |
|---|---|
en | Friendly, direct, conversational. Use contractions ("you've", "can't"). No formal passive voice. |
ja | Use polite but natural keigo (〜ます/〜です form). Avoid literal translations of English idioms. Use short, clear sentences. Don't overuse katakana loanwords when Japanese alternatives exist. |
ko | Polite 합쇼체 or 해요체 depending on the app's audience. Avoid Konglish where natural Korean exists. |
zh-TW / zh-CN | Traditional vs simplified — do not mix. Natural, not literal. Check that date/number formats are locale-correct. |
fr | Formal vous for most apps; tu only for clearly youth/casual products. Gender agreement must be correct — check nouns. |
de | German compounds can get long — check for truncation. Formal Sie for business apps. |
es | Check for tú/usted/vosotros consistency. Latin American vs Spain variants differ. |
RTL (ar, he) | Mirror layout. Check that numbers are LTR within RTL text. Verify UI wraps correctly. |
For each active locale:
// Set locale via URL or cookie depending on the app
await page.goto('http://localhost:3000/ja'); // or set cookie
await browser_snapshot(); // capture the stateLook for:
common.save)$10.00 vs 10,00 € vs ¥1,000)For every hardcoded string found in Phase 1, create an i18n key and replace it:
// Before (react-i18next)
<p>Welcome back, {name}!</p>
// After
const { t } = useTranslation('common');
<p>{t('welcome_back', { name })}</p>// en/common.json
"welcome_back": "Welcome back, {{name}}!"
// ja/common.json
"welcome_back": "おかえりなさい、{{name}}さん!"Edit the translation files directly. Prioritise:
For any key missing in a non-English locale, add a natural translation. If you don't have a native speaker for a locale, use a clearly-marked placeholder:
"some_key": "[TODO: needs native Japanese review] {{count}} items selected"Use the i18n library's format functions rather than manual formatting:
// react-i18next with i18next-icu or i18next-intervalPlural
t('items_count', { count: 5 }) // handles plural forms automatically
// Intl API for dates and numbers (framework-agnostic)
new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(date);
new Intl.NumberFormat(locale, { style: 'currency', currency: 'JPY' }).format(1234);## i18n Audit Report — [App] — [Date]
### Active locales
[list with coverage % — keys present / total keys]
### Hardcoded strings found
| File | Line | String | Action |
|------|------|--------|--------|
| src/components/Foo.tsx | 42 | "Save" | Extracted to key `buttons.save` |
### Translation quality issues fixed
| Locale | Key | Before | After | Issue type |
|--------|-----|--------|-------|-----------|
| ja | errors.email_invalid | "無効なメールアドレス" | "メールアドレスが正しくないようです。入力内容をご確認ください。" | Too literal, not helpful |
### Missing keys added
[count per locale]
### Formatting fixes
[date/number/currency issues found and fixed]
### Still needs native review
[keys marked [TODO] that need a human native speaker]~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.