Severity: Warning — does not affect exit code or fail CI builds.
Glot detects translation keys that cannot be statically analyzed because they use variables, template literals with expressions, or other dynamic patterns.
Detection Rule
A translation key is flagged as unresolved if it:
Uses a variable as the key argument: t(variableName)
Uses a template literal with expressions : t(`prefix.${dynamic}`)
Uses computed properties : t(keys[index])
Uses function calls : t(getKey())
Is not covered by a glot-message-keys annotation
Unresolved keys can’t be checked for existence, potentially causing missing-key errors at runtime.
What Gets Detected
Variable Keys
Keys stored in variables:
// Unresolved
const key = getUserPreference ();
< button > { t ( key ) } </ button >
// Also unresolved
const statusKey = `status. ${ code } ` ;
< span > { t ( statusKey ) } </ span >
Template Literals
Template strings with dynamic expressions:
// Unresolved
< div > { t ( `notifications. ${ type } ` ) } </ div >
// Also unresolved
< span > { t ( `errors. ${ errorCode } .message` ) } </ span >
Computed Properties
Array or object access:
// Unresolved
const keys = [ 'home' , 'about' , 'contact' ];
< a > { t ( keys [ 0 ]) } </ a >
// Also unresolved
const translations = { title: 'page.title' };
< h1 > { t ( translations . title ) } </ h1 >
Function Calls
Keys returned from functions:
// Unresolved
function getGreeting ( time ) {
return time < 12 ? 'greetings.morning' : 'greetings.evening' ;
}
< h1 > { t ( getGreeting ( hour )) } </ h1 >
warning: (unresolved) unresolved-key
--> ./src/components/Status.tsx:12:15
|
| Cannot statically resolve translation key (uses variable or template literal)
|
The warning indicates the location but can’t show the actual key since it’s computed at runtime.
Severity
Warning - Unresolved keys are informational because:
They might be valid at runtime
They could lead to missing-key errors
Glot cannot verify if the keys exist in locale files
They prevent other checks like unused and orphan from running safely
Why This Matters
Static Analysis Limitations
Glot can’t verify unresolved keys exist:
// Glot can verify this
< button > { t ( 'common.submit' ) } </ button > // ✓ Checked
// Glot cannot verify this
< button > { t ( `common. ${ action } ` ) } </ button > // ⚠ Unresolved
If action is “submit”, the key works. If it’s “submet” (typo), you get a runtime error that glot can’t detect.
Prevents Cleanup
Unresolved keys block glot clean:
$ npx glot clean
Error: Cannot safely clean unused keys because some keys are resolved dynamically.
Use glot-message-keys annotations to declare dynamic keys.
This is a safety feature to prevent accidentally deleting keys that are used dynamically.
Incomplete Coverage
Other checks can’t analyze unresolved keys:
// glot can't check if these keys exist
const status = getStatus ();
< span > { t ( `status. ${ status } ` ) } </ span >
Missing keys only appear at runtime, not during static analysis.
How to Fix
Option 1: Add glot-message-keys Annotation (Recommended)
Declare the possible keys explicitly:
// Before (unresolved)
< span > { t ( `status. ${ code } ` ) } </ span >
// After (resolved)
// glot-message-keys "status.active", "status.inactive", "status.pending"
< span > { t ( `status. ${ code } ` ) } </ span >
Now glot can verify these keys exist and track their usage.
Option 2: Use Glob Patterns
For many similar keys, use wildcards:
// Before (unresolved)
< div > { t ( `errors. ${ errorCode } .message` ) } </ div >
// After (resolved with pattern)
// glot-message-keys "errors.*.message"
< div > { t ( `errors. ${ errorCode } .message` ) } </ div >
Glot will match any key like errors.404.message, errors.500.message, etc.
Option 3: Use glot fix Command
Automatically insert annotations:
# Preview what will be added
npx glot fix
# Apply the fixes
npx glot fix --apply
The fix command analyzes your code and template patterns to suggest appropriate annotations.
Option 4: Refactor to Static Keys
When possible, use static keys with conditionals:
// Before (dynamic)
< span > { t ( `status. ${ status } ` ) } </ span >
// After (static)
< span >
{ status === 'active' && t ( 'status.active' ) }
{ status === 'inactive' && t ( 'status.inactive' ) }
{ status === 'pending' && t ( 'status.pending' ) }
</ span >
// Or with object lookup
const statusKeys = {
active: 'status.active' ,
inactive: 'status.inactive' ,
pending: 'status.pending'
} as const ;
< span > { t ( statusKeys [ status ]) } </ span >
While the object lookup is still dynamic, you can annotate it once:
// glot-message-keys "status.active", "status.inactive", "status.pending"
const statusKeys = { /* ... */ };
glot-message-keys Annotation
Use glot-message-keys comments to declare which keys a dynamic expression uses:
// Exact keys
// glot-message-keys "auth.login", "auth.logout", "auth.register"
< button > { t ( `auth. ${ action } ` ) } </ button >
// Glob patterns
// glot-message-keys "errors.*.title", "errors.*.message"
< h2 > { t ( `errors. ${ code } .title` ) } </ h2 >
Place annotations before the dynamic key usage. For full syntax reference (relative patterns, glob matching, JSX comments, multiple annotations), see Directives — Dynamic Key Declaration .
Common Patterns
Status Messages
// glot-message-keys "status.loading", "status.success", "status.error"
function StatusMessage ({ status } : { status : 'loading' | 'success' | 'error' }) {
return < div > { t ( `status. ${ status } ` ) } </ div > ;
}
Error Messages
// glot-message-keys "errors.*.title", "errors.*.description"
function ErrorDisplay ({ code } : { code : number }) {
return (
< div >
< h1 > { t ( `errors. ${ code } .title` ) } </ h1 >
< p > { t ( `errors. ${ code } .description` ) } </ p >
</ div >
);
}
Dynamic Pages
// glot-message-keys "pages.home.title", "pages.about.title", "pages.contact.title"
function PageTitle ({ page } : { page : string }) {
return < title > { t ( `pages. ${ page } .title` ) } </ title > ;
}
Pluralization (Avoid Dynamic Keys)
Don’t use dynamic keys for plurals:
// ❌ Bad - uses dynamic keys
const key = count === 1 ? 'item' : 'items' ;
< span > { t ( key ) } </ span >
// ✅ Good - use ICU message format
< span > { t ( 'items' , { count }) } </ span >
// In locale file:
// "items": "{count, plural, =1 {1 item} other {# items}}"
Examples
Before and After: Status Component
Before (unresolved): function OrderStatus ({ status } : { status : string }) {
// ⚠ Unresolved - glot can't verify these keys exist
return (
< div className = { `status- ${ status } ` } >
< span > { t ( `order.status. ${ status } ` ) } </ span >
< p > { t ( `order.description. ${ status } ` ) } </ p >
</ div >
);
}
After (resolved): // glot-message-keys "order.status.*", "order.description.*"
function OrderStatus ({ status } : { status : string }) {
// ✓ Resolved - glot can verify keys match this pattern
return (
< div className = { `status- ${ status } ` } >
< span > { t ( `order.status. ${ status } ` ) } </ span >
< p > { t ( `order.description. ${ status } ` ) } </ p >
</ div >
);
}
Now glot can:
Verify order.status.pending, order.status.shipped, etc. exist
Check that keys aren’t orphaned
Enable glot clean to safely remove unused keys
Before (unresolved): function Notification ({ type , priority } : Props ) {
// Multiple unresolved keys
return (
< div className = { `notification- ${ priority } ` } >
< Icon > { t ( `icons. ${ type } ` ) } </ Icon >
< h3 > { t ( `notifications. ${ type } . ${ priority } .title` ) } </ h3 >
< p > { t ( `notifications. ${ type } . ${ priority } .message` ) } </ p >
< button > { t ( `actions. ${ type } .dismiss` ) } </ button >
</ div >
);
}
After (resolved): // glot-message-keys "icons.info", "icons.warning", "icons.error"
// glot-message-keys "notifications.*.*.title", "notifications.*.*.message"
// glot-message-keys "actions.*.dismiss"
function Notification ({ type , priority } : Props ) {
return (
< div className = { `notification- ${ priority } ` } >
< Icon > { t ( `icons. ${ type } ` ) } </ Icon >
< h3 > { t ( `notifications. ${ type } . ${ priority } .title` ) } </ h3 >
< p > { t ( `notifications. ${ type } . ${ priority } .message` ) } </ p >
< button > { t ( `actions. ${ type } .dismiss` ) } </ button >
</ div >
);
}
Best Practices
1. Minimize Dynamic Keys
Prefer static keys when possible:
// Instead of:
t ( `color. ${ color } ` )
// Consider:
const colorKeys = {
red: 'color.red' ,
blue: 'color.blue' ,
green: 'color.green'
} as const ;
t ( colorKeys [ color ])
2. Document Patterns
Explain complex patterns in comments:
// Dynamic routing pattern for blog posts
// glot-message-keys "blog.*.title", "blog.*.excerpt", "blog.*.content"
function BlogPost ({ slug } : { slug : string }) {
return (
< article >
< h1 > { t ( `blog. ${ slug } .title` ) } </ h1 >
< p > { t ( `blog. ${ slug } .excerpt` ) } </ p >
< div > { t ( `blog. ${ slug } .content` ) } </ div >
</ article >
);
}
3. Keep Annotations Close
Place annotations near the code they describe:
// ✓ Good - annotation right before usage
function Component () {
// glot-message-keys "prefix.*"
return < div > { t ( `prefix. ${ key } ` ) } </ div > ;
}
// ✗ Bad - annotation far from usage
// glot-message-keys "prefix.*"
function Component () {
const [ state , setState ] = useState ();
useEffect (() => { /* ... */ });
return < div > { t ( `prefix. ${ key } ` ) } </ div > ; // Easy to miss
}
4. Use TypeScript for Safety
Combine with TypeScript for type-safe keys:
type StatusKey = 'active' | 'inactive' | 'pending' ;
// glot-message-keys "status.active", "status.inactive", "status.pending"
function Status ({ status } : { status : StatusKey }) {
return < span > { t ( `status. ${ status } ` ) } </ span > ;
}