From co-dev
Manage Loco (localise.biz) translation assets. Use when the user wants to create a translation key, delete a translation key, translate text, manage localization, add a Loco asset, remove unused translations, scan for unused tokens, or manage i18n keys. Supports create, delete, and scan commands with multi-project support and auto-translation.
npx claudepluginhub cloud-officer/claude-code-plugin-dev --plugin co-devThis skill is limited to using the following tools:
Manage translation assets on Loco (localise.biz) from the CLI. Supports three commands:
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Manage translation assets on Loco (localise.biz) from the CLI. Supports three commands:
This skill uses Loco API keys set as environment variables. The naming convention supports multiple projects:
LOCO_API_KEY_<PROJECT> — Per-project key (e.g., LOCO_API_KEY_IOS, LOCO_API_KEY_ANDROID, LOCO_API_KEY_WEB)LOCO_API_KEY — Single-project fallback when only one project existsAll API calls use the Authorization header. Never pass the API key as a query parameter.
Authorization: Loco $LOCO_KEY
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/auth/verify
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/assets
curl -s -f -H "Authorization: Loco $LOCO_KEY" "https://localise.biz/api/assets/$ASSET_ID"
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "id=$KEY&text=$TEXT&type=text&context=$CONTEXT" \
https://localise.biz/api/assets
curl -s -f -X DELETE -H "Authorization: Loco $LOCO_KEY" \
"https://localise.biz/api/assets/$ASSET_ID"
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: text/plain" \
--data-raw "$TRANSLATION_TEXT" \
"https://localise.biz/api/translations/$ASSET_ID/$LOCALE"
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=$TAG_NAME" \
"https://localise.biz/api/assets/$ASSET_ID/tags"
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/locales
Detect which Loco project(s) are configured by scanning environment variables.
env | grep -o '^LOCO_API_KEY[^=]*'
Use
grep -oto print only the variable names, never the values. The keys themselves must never appear in transcripts or logs.
If no variables found:
LOCO_API_KEY (single project) or LOCO_API_KEY_<PROJECT> (multi-project) as an environment variable. You can find your API key at https://localise.biz under Project > API Keys."If exactly one variable found:
LOCO_API_KEY_IOS → project "IOS"), or "default" if using LOCO_API_KEY.If multiple variables found:
/loco create --project ios "key" "text"), use that directly.Verify the key works:
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/auth/verify
If verification fails, tell the user the API key is invalid and stop.
Important: After resolving the key, store it in a shell variable LOCO_KEY for all subsequent API calls. Never echo or log the key value.
Use this flow when the user wants to create a new translation key.
Extract from the user's request:
settings.notifications.title)"Notification Settings")"Title of the notifications settings page")"settings,v2.1")Fetch existing assets and check if the key already exists. Capture the HTTP status separately so we can distinguish "not found" (good — we can create) from a real error:
STATUS=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Loco $LOCO_KEY" \
"https://localise.biz/api/assets/$(echo -n "$KEY" | jq -sRr @uri)")
STATUS == 200 → key already exists. Tell the user: "Key $KEY already exists. Use a different key or delete the existing one first." Stop.STATUS == 404 → key does not exist; proceed to Step 4.Do not use curl -f here — -f collapses 404 into a generic non-zero exit and hides the case we actually want to detect.
Fetch a sample of existing keys to detect the project's naming patterns:
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/assets | jq -r '.[0:50] | .[].id'
Analyze the sample for:
settings.title), underscores (settings_title), or camelCase (settingsTitle)login_button) vs nested (auth.login.button)screen., feature., error.If the proposed KEY deviates from detected conventions, warn the user. For example:
"Existing keys use dot-separated lowercase (e.g.,
settings.notifications.enabled). Your keySettings_Notifications_Titledoesn't match. Suggested:settings.notifications.title. Proceed anyway?"
If the user confirms or the key matches conventions, continue.
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "id=$KEY" \
--data-urlencode "text=$TEXT" \
-d "type=text" \
--data-urlencode "context=$CONTEXT" \
https://localise.biz/api/assets
If the creation fails, report the error and stop.
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: text/plain" \
--data-raw "$TEXT" \
"https://localise.biz/api/translations/$(echo -n "$KEY" | jq -sRr @uri)/en"
If --no-translate was specified, skip this step.
Fetch all project locales:
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/locales | jq -r '.[].code'
Filter out English locales (any starting with en).
For each remaining locale:
Translate the English text using Claude. Provide the locale code, source text, any context, and placeholder preservation rules (see Placeholder Preservation Rules below).
Validate placeholders — Extract all placeholders from the source text and verify they appear identically in the translation. If validation fails, skip this locale and include it in the error report.
Push the translation:
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: text/plain" \
--data-raw "$TRANSLATED_TEXT" \
"https://localise.biz/api/translations/$(echo -n "$KEY" | jq -sRr @uri)/$LOCALE"
Always tag with needs-review:
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=needs-review" \
"https://localise.biz/api/assets/$(echo -n "$KEY" | jq -sRr @uri)/tags"
If --tags was provided, also apply each custom tag:
curl -s -f -X POST -H "Authorization: Loco $LOCO_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=$TAG" \
"https://localise.biz/api/assets/$(echo -n "$KEY" | jq -sRr @uri)/tags"
Display a summary table:
Key: settings.notifications.title
Text: Notification Settings
Project: IOS
Translations:
| Locale | Translation | Status |
|--------|--------------------------|--------|
| en | Notification Settings | Set |
| fr | Paramètres de notification | Auto |
| de | Benachrichtigungseinstellungen | Auto |
| es | Configuración de notificaciones | Auto |
| ja | 通知設定 | Auto |
Tags: needs-review, settings
If any locales failed placeholder validation, include a warnings section:
Warnings:
- ar: Skipped — placeholder mismatch (expected %d, got none)
Use this flow when the user wants to delete a translation key.
curl -s -f -H "Authorization: Loco $LOCO_KEY" "https://localise.biz/api/assets/$(echo -n "$KEY" | jq -sRr @uri)"
If the asset is not found (HTTP 404), tell the user: "Key $KEY not found." and stop.
Display the asset details to the user:
This confirmation is mandatory. Never skip it.
Ask the user: "Are you sure you want to delete $KEY? This will remove the asset and all its translations. This cannot be undone."
If the user does not confirm, stop here.
curl -s -f -X DELETE -H "Authorization: Loco $LOCO_KEY" \
"https://localise.biz/api/assets/$(echo -n "$KEY" | jq -sRr @uri)"
Report: "Deleted $KEY and all its translations."
If the deletion fails, report the error.
Use this flow when the user wants to find unused translation keys in the codebase.
curl -s -f -H "Authorization: Loco $LOCO_KEY" https://localise.biz/api/assets | jq -r '.[].id'
Save the full list of keys.
Auto-detect which platforms exist in the repository by checking for indicator files:
| Platform | Indicator Files |
|---|---|
| iOS/macOS | *.xcodeproj, *.xcworkspace, Package.swift, *.strings, *.stringsdict |
| Android | AndroidManifest.xml, build.gradle, build.gradle.kts, strings.xml |
| Rails | Gemfile with rails, config/locales/, *.yml locale files |
| Web (JS/TS) | package.json, i18n/, locales/, *.vue, *.tsx, *.jsx |
| Flutter | pubspec.yaml, *.dart, *.arb |
| .NET | *.csproj, *.resx |
| Go | go.mod |
| Python | requirements.txt, pyproject.toml, django, gettext |
For each detected platform, search the codebase using platform-appropriate patterns:
iOS/macOS:
NSLocalizedString("KEY"
String(localized: "KEY"
"KEY" = " (in .strings files)
Android:
R.string.KEY
@string/KEY
<string name="KEY"
Rails:
I18n.t("KEY"
I18n.t('KEY'
t("KEY"
t('KEY'
t(:KEY
Web (JS/TS/Vue):
$t("KEY"
$t('KEY'
t("KEY"
t('KEY'
i18n.t("KEY"
i18n.t('KEY'
Flutter:
AppLocalizations.of(context).KEY
"KEY" (in .arb files)
Efficient batch approach:
Important: For large key sets (>100 keys), search in batches using grep with multiple patterns to avoid per-key overhead:
# Build a pattern file from all keys and grep in one pass
echo "$ALL_KEYS" | grep -F -r -l -f - --include="*.swift" --include="*.m" --include="*.strings" --include="*.xml" --include="*.rb" --include="*.yml" --include="*.js" --include="*.ts" --include="*.vue" --include="*.jsx" --include="*.tsx" --include="*.dart" --include="*.arb" --include="*.resx" --include="*.go" --include="*.py" .
Or use the Grep tool for each key individually if the key set is small (<100 keys).
Display a summary:
Loco Scan Report
================
Project: IOS
Total assets: 342
Referenced: 318
Unused: 24
Potentially Unused Keys:
| # | Key | Source Text |
|---|-------------------------------|--------------------------|
| 1 | legacy.old_feature.title | Old Feature |
| 2 | deprecated.screen.description | This screen is deprecated|
| ... |
Caveats:
- Dynamic keys (e.g., `t("error.#{code}")`) cannot be detected by static analysis.
- Keys used in configuration files, backend templates, or external services may not appear in the codebase.
- Review this list manually before deleting any keys.
When translating text, all placeholders must be preserved exactly as they appear in the source. The following placeholder formats must be detected and validated:
%@ — Object (string)%d — Integer%f — Float%ld, %lld — Long/long long%1$@, %2$d — Positional arguments%{name} — Named interpolation%<name>s — Formatted named interpolation%1$s, %2$d — Positional arguments%s, %d — Sequential arguments{name} — Simple replacement{count, plural, one {# item} other {# items}} — Plural rules{gender, select, male {He} female {She} other {They}} — Select rules<b>, <a href="...">, <br/>, etc.) — preserve exactlyAfter generating a translation, extract all placeholders from both source and translation using regex patterns:
%(\d+\$)?[@ dfsld]+ and %% (escaped percent)%\{[^}]+\} and %<[^>]+>[sdfi]%(\d+\$)?[sd]\{[^}]+\}<[^>]+>Compare the sets. If any placeholder is missing or added in the translation, the validation fails. Skip that locale and report it.
$LOCO_KEY in commands but never print its value. If debugging, show LOCO_API_KEY_*** is set instead.?key=...). Always use Authorization: Loco $LOCO_KEY.needs-review tag.needs-review to all assets that receive auto-translations.LOCO_API_KEY_* variables and prompt the user to select one if needed.