frontend-i18n
Skill0 starsChanged 4 months agoMITView on GitHub
Translation and i18n rules for the AMIS admin project. Use this whenever writing any user-visible string, building a form with validation messages, handling API or business errors, or adding new translation keys. Also use when the user asks 'how do I translate X', 'add translation for Y', 'don't hardcode this string', or 'add an error message'. Covers: useTranslation usage, Zod schema factory pattern, validation key structure, API error translation via useAPIErrorTranslator, and adding new error codes to both translation files.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- lien0219/openxflowtheir own version, first seen Nov 8, 2026
- rommapp/rommtheir own version, first seen Oct 8, 2026
- langflow-ai/langflowtheir own version, first seen Oct 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- langflow-ai/langflow★ 156ksame name
- rommapp/romm★ 14ksame name
- lien0219/openxflow★ 8same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository12 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- feature-architectureSkill
- frontend-auditSkill
- mui-themeSkill
- react-coreSkill
- react-standardSkill
- table-mrtSkill
- tanstack-formSkill
- tanstack-querySkill
- tanstack-routerSkill
- flutter-clean-arch-getxSkill
- flutter-flavorsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. 0 are common, 3 are distinctive, and 11 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 12
Default language: lo (Lao).
- Very specific: line 18
Always call useTranslation() at the top of the component.
- Very specific: line 105
If you need a validation message that doesn't fit any of these, add a new key under validation.* in both en/translation.json and …
- Very specific: line 146
When the API returns a 422 with validation details ({ field, tag, param }), translateError maps the tag to apiValidation.{tag}:
- Very specific: line 166
When the backend returns a new error code (e.g., CONTRACT_ALREADY_SIGNED), add it in two places.
- Very specific: line 186
If the error belongs in errors.businessLogic.* (not errors.invalidArgument.*), add the key name to the businessLogic check inside …
- Distinctive: line 214
Always add keys to both files at the same time.
- Very specific: line 241
Use camelCase for all keys: createSuccess, not create_success or CreateSuccess
- Distinctive: line 249
When adding any new user-visible text:
- Very specific: line 251
Add the English key to en/translation.json
- Very specific: line 252
Add the Lao key to lo/translation.json
- Very specific: line 253
Use t('key.path') in the component — never the raw string
- Distinctive: line 254
If it's a validation message in a Zod schema, use the factory function pattern
- Very specific: line 255
If it's an API error code, register it in useAPIErrorTranslator.ts + both translation files
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Translation & i18n
The project uses react-i18next with a single translation namespace. All user-visible text — labels, placeholders, validation messages, snackbar messages, error strings — must go through t(). Never hardcode English (or Lao) strings directly in components or schemas.
Config: src/core/config/i18n.config.ts
Translation files: src/assets/locales/en/translation.json and src/assets/locales/lo/translation.json
Default language: lo (Lao). Fallback: lo.
1. Using t() in Components
Always call useTranslation() at the top of the component. Pass the result of t() everywhere a string is displayed to the user.
const { t } = useTranslation()
// Labels, buttons, headings
<Button>{t('common.save')}</Button>
<Typography>{t('ministries.edit')}</Typography>
// Dynamic values
<Typography>{t('dashboard.welcome', { name: user.name })}</Typography>
// Option lists — store the key, translate at render time
const GROUP_OPTIONS = [
{ value: 'CENTER', label: 'ministries.groups.CENTER' },
{ value: 'PROVINCE', label: 'ministries.groups.PROVINCE' },
]
{GROUP_OPTIONS.map(opt => <MenuItem key={opt.value}>{t(opt.label)}</MenuItem>)}Never do this:
// Bad — hardcoded string
<Button>Save</Button>
label="ຊື່"
// Bad — string in a constant without t()
const label = 'Province'2. Zod Validation Schemas — Factory Function Pattern
Schemas that produce user-visible error messages must be factory functions that accept t: TFunction. This lets them produce translated messages at runtime.
import { z } from 'zod'
import type { TFunction } from 'i18next'
export const getFeatureSchema = (t: TFunction) => z.object({
name: z
.string()
.min(1, t('validation.required'))
.max(255, t('validation.maxLength', { max: 255 })),
code: z
.string()
.max(50, t('validation.maxLength', { max: 50 }))
.optional()
.or(z.literal('')),
group: z.enum(['A', 'B'], {
message: t('validation.required'),
}),
})
export type FeatureFormData = z.infer<ReturnType<typeof getFeatureSchema>>In the form component, call the factory with t:
const { t } = useTranslation()
const schema = getFeatureSchema(t)
const form = useAppForm({
defaultValues: { name: '', code: '', group: 'A' } as FeatureFormData,
validators: { onChange: schema },
onSubmit: async ({ value }) => { ... }
})Available validation.* keys
These shared keys live at the top level of translation.json:
| Key | Parameters | Use for |
validation.required | — | Field is empty / required |
validation.minLength | { min } | Minimum character count |
validation.maxLength | { max } | Maximum character count |
validation.email | — | Email format |
validation.number | — | Must be a number |
validation.positive | — | Must be positive |
validation.min | { min } | Numeric minimum |
validation.max | { max } | Numeric maximum |
validation.passwordMismatch | — | Password confirmation |
If you need a validation message that doesn't fit any of these, add a new key under validation.* in both en/translation.json and lo/translation.json.
For feature-specific validation messages that only apply to one domain, put them under featureName.validation.*:
// in translation.json
"ministries": {
"validation": {
"codeFormat": "Code must be alphanumeric"
}
}3. API Error Translation
The project uses useAPIErrorTranslator (at src/shared/hooks/useAPIErrorTranslator.ts) to translate server errors into user-friendly strings. Use it in every mutation onError callback.
import { useAPIErrorTranslator } from '@/shared/hooks'
const { translateError } = useAPIErrorTranslator()
createMutation.mutate(value, {
onSuccess: () => showSuccess(t('feature.createSuccess')),
onError: (error) => showError(translateError(error)),
})How translateError works (priority order)
- Network / no response →
errors.http.503orerrors.http.504 - HTTP 422 with
errorsarray → field-level validation viaapiValidation.* - Legacy
detailarray → same field-level validation statuscode present → business error viaerrors.{category}.*- HTTP status code →
errors.http.{status} - Fallback → raw message or
errors.general.unexpected
apiValidation.* keys — server-side field errors
When the API returns a 422 with validation details ({ field, tag, param }), translateError maps the tag to apiValidation.{tag}:
| Key | Parameters | Tag |
apiValidation.required | { field } | required |
apiValidation.min | { field, n } | min |
apiValidation.max | { field, n } | max |
apiValidation.email | { field } | email |
apiValidation.gt | { field, n } | gt |
apiValidation.gte | { field, n } | gte |
apiValidation.numeric | { field } | numeric |
apiValidation.alpha | { field } | alpha |
apiValidation.alphanum | { field } | alphanum |
Field names in the error output come from fields.{fieldName}. Add new field names to fields.* in both translation files.
4. Adding New Business Errors
When the backend returns a new error code (e.g., CONTRACT_ALREADY_SIGNED), add it in two places.
Step 1: Add to useAPIErrorTranslator.ts
Find the right translate*Error function and add the code to its errorCodeMap:
// In translateInvalidArgumentError:
const errorCodeMap: Record<string, string> = {
// ... existing entries
'CONTRACT_ALREADY_SIGNED': 'contractAlreadySigned', // add here
}Which function to use:
translateNotFoundError→ statusNOT_FOUND(resource doesn't exist)translateAlreadyExistsError→ statusALREADY_EXISTS(duplicate)translateInvalidArgumentError→ statusINVALID_ARGUMENT(bad state / business rule violation)translateBusinessErrorin theINTERNAL/UNAVAILABLEcases → for service-level errors
If the error belongs in errors.businessLogic.* (not errors.invalidArgument.*), add the key name to the businessLogic check inside translateInvalidArgumentError:
if (['resourceFeeAlreadyPaid', 'contractAlreadySigned'].includes(errorType)) {
return t(`errors.businessLogic.${errorType}`)
}Step 2: Add translation keys to both files
In src/assets/locales/en/translation.json:
"errors": {
"invalidArgument": {
"contractAlreadySigned": "Contract has already been signed"
}
}In src/assets/locales/lo/translation.json:
"errors": {
"invalidArgument": {
"contractAlreadySigned": "ສັນຍານີ້ໄດ້ຖືກລົງນາມແລ້ວ"
}
}Always add keys to both files at the same time.
5. common.errors.* — Module-Specific Export/Operation Errors
There is a flat error map under common.errors used for module-specific operation codes (e.g. VEHICLE_ALREADY_EXPORTED). These are used directly in components, not through useAPIErrorTranslator:
// Used in ExportDialog components
const errorKey = `common.errors.${error.code}`
showError(t(errorKey, { defaultValue: error.message }))To add a new one, add to common.errors in both translation files:
"common": {
"errors": {
"LAND_ALREADY_EXPORTED": "Land record has already been exported"
}
}6. Translation Key Naming Conventions
- Use camelCase for all keys:
createSuccess, notcreate_successorCreateSuccess - Feature-level keys nest under the feature name:
ministries.createSuccess - Shared labels go under
common.*orfields.* - Validation messages:
validation.*(generic) orfeatureName.validation.*(specific) - Error messages:
errors.{category}.{key}— never put error strings in the feature namespace
7. Adding Translation Keys Checklist
When adding any new user-visible text:
- Add the English key to
en/translation.json - Add the Lao key to
lo/translation.json - Use
t('key.path')in the component — never the raw string - If it's a validation message in a Zod schema, use the factory function pattern
- If it's an API error code, register it in
useAPIErrorTranslator.ts+ both translation files
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

