Skip to content

feat(platform): add i18n support for agent config fields#943

Merged
larryro merged 3 commits into
mainfrom
feat/agent-i18n
Apr 2, 2026
Merged

feat(platform): add i18n support for agent config fields#943
larryro merged 3 commits into
mainfrom
feat/agent-i18n

Conversation

@larryro

@larryro larryro commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add an i18n key to the agent JSON schema for per-locale overrides of displayName, description, and conversationStarters
  • Add org-level default locale setting in organization settings with a language selector
  • Resolve locale-specific agent fields in chat based on the user's current locale and the org default
  • Add locale tabs and AI-powered auto-translate button in the conversation starters editor
  • Include German (de) translations in the example chat-agent.json

Test plan

  • Verify the org settings page shows the new "Default language" selector and persists the choice
  • Confirm conversation starters editor displays locale tabs (default + additional locales)
  • Test auto-translate button translates default-locale starters into the selected locale
  • Verify chat resolves the correct locale-specific starters based on user language and org default
  • Validate the agent JSON schema accepts the new i18n field and rejects malformed entries

Summary by CodeRabbit

  • New Features
    • Added multilingual agent support with initial German language support
    • Added organization-level default language configuration
    • Added multi-language editor for agent conversation starters
    • Added automatic translation feature to generate conversation starter translations across supported languages
    • Agents now support language-specific customization of display names and descriptions

Add locale-based translations for agent fields (displayName, description,
conversationStarters) with an i18n key in the agent JSON schema. Includes
org-level default locale setting, locale-aware field resolution in chat,
auto-translate via AI in the conversation starters editor, and locale
tab UI for managing translations per language.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces internationalization support for agents, enabling per-locale overrides of agent metadata fields (displayName, description, conversationStarters). The changes extend the agent JSON schema to include an optional i18n record, add organization-level default locale configuration, implement locale-aware agent field resolution with fallback logic, and create an auto-translation feature for conversation starters using OpenAI. The UI for editing conversation starters is enhanced to support locale-specific editing and translation. The backend delegates locale resolution to a dedicated utility function that prioritizes locale-specific overrides over top-level fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(platform): add i18n support for agent config fields' is clear, specific, and directly reflects the main objective of the changeset: adding internationalization support for agent configuration fields.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-i18n

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/features/chat/hooks/use-effective-agent.ts`:
- Around line 44-50: The resolve function should normalize runtime locale tags
to their base language before calling resolveAgentLocale to avoid mismatches
with base-only agent.i18n keys; update the code in use-effective-agent.ts
(inside the resolve function where locale and defaultLocale are used) to derive
baseLocale (e.g., strip region subtags like "de-DE" -> "de") and
baseDefaultLocale and pass those normalized values into
resolveAgentLocale(agent, baseLocale, baseDefaultLocale) so that agents (from
agents and agent.i18n) match regardless of region-specific runtime tags.

In
`@services/platform/app/features/settings/organization/components/organization-settings.tsx`:
- Around line 90-94: The form currently seeds defaultLocale from
existingMetadata.defaultLocale without checking it is one of
SUPPORTED_AGENT_LOCALES, so validate that value before using it: when
initializing defaultLocale (the block referencing existingMetadata.defaultLocale
and DEFAULT_LOCALE) check membership in SUPPORTED_AGENT_LOCALES and fall back to
DEFAULT_LOCALE if not supported; also ensure any save/update path that persists
defaultLocale performs the same validation to avoid re-persisting unsupported
legacy locales (this will prevent resolveAgentLocale() from looking up missing
keys in the agent i18n record).

In
`@services/platform/app/routes/dashboard/`$id/agents/$agentId/conversation-starters.tsx:
- Around line 54-62: The duplicate parseDefaultLocale function (and its
DEFAULT_LOCALE constant) should be extracted into a single shared utility
module: move the logic into an exported function parseDefaultLocale and export
DEFAULT_LOCALE from a common utility, then replace the local implementations in
both conversation-starters.tsx and use-effective-agent.ts with imports from that
utility; ensure the exported function signature and behavior remain identical
and update imports where parseDefaultLocale and DEFAULT_LOCALE are referenced.
- Around line 234-267: The locale tab buttons rendered in the localeTabs map
(button with onClick calling setEditingLocale, key={locale}) lack an explicit
focus-visible style; update the className passed into cn for that button to
include keyboard-focus styles (e.g., focus:outline-none and focus-visible:ring /
focus-visible:ring-offset / focus-visible:ring-foreground with an accessible
ring color and offset) so the active/focus state is visible for keyboard users
while preserving the existing active/hover classes; ensure the focus rules apply
to the same button element that uses hasLocaleContent, tGlobal and t so keyboard
users can clearly see which tab is focused.
- Around line 89-94: getStarters is recreated on every render because it's
declared inside the component; memoize it with React.useMemo (depend on
editingLocale and config) or move the logic into a stable helper to avoid
redefinition. Specifically, wrap the logic that reads
config.conversationStarters and
config.i18n?.[editingLocale]?.conversationStarters in useMemo (dependencies:
[editingLocale, config]) so getStarters returns the memoized array and is not
recreated each render.

In `@services/platform/lib/shared/constants/agents.ts`:
- Line 5: The exported type AgentLocale is unused and causing a lint failure;
either remove the unused export or consume it where appropriate. Locate the
declaration "export type AgentLocale = (typeof
SUPPORTED_AGENT_LOCALES)[number];" and either (a) delete the export (or the
entire type line) if no code references it, or (b) replace the unused export
with an internal (non-exported) type or use AgentLocale in the code that defines
or types agent-related functions/variables (e.g., function signatures, params,
or constants referencing SUPPORTED_AGENT_LOCALES) so it is consumed; ensure the
change removes the unused-export lint error without altering runtime behavior.

In `@services/platform/lib/shared/schemas/agents.ts`:
- Line 25: Remove the unused exported type TranslatableFields to satisfy the
linter: delete the line exporting TranslatableFields (which references
translatableFieldsSchema) or, if the type will be needed internally only,
convert it to a non-exported type (e.g., change "export type TranslatableFields"
to "type TranslatableFields") so the symbol is no longer exported and the
unused-export lint error is resolved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b6ac8bc-072a-410e-8277-61d818770689

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2c659 and efb43c1.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (13)
  • examples/agents/chat-agent.json
  • services/platform/app/features/agents/hooks/mutations.ts
  • services/platform/app/features/chat/hooks/queries.ts
  • services/platform/app/features/chat/hooks/use-effective-agent.ts
  • services/platform/app/features/settings/organization/components/organization-settings.tsx
  • services/platform/app/routes/dashboard/$id/agents/$agentId/conversation-starters.tsx
  • services/platform/convex/agents/file_actions.ts
  • services/platform/convex/agents/file_utils.ts
  • services/platform/convex/agents/translate_fields.ts
  • services/platform/lib/shared/constants/agents.ts
  • services/platform/lib/shared/schemas/agents.ts
  • services/platform/lib/shared/utils/resolve-agent-locale.ts
  • services/platform/messages/en.json

Comment on lines +44 to +50
const locale = i18n.language;
const defaultLocale = parseDefaultLocale(organization?.metadata);

return useMemo(() => {
function resolve(agent: NonNullable<typeof agents>[number]) {
const resolved = resolveAgentLocale(agent, locale, defaultLocale);
return {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

In i18next/react-i18next, can i18n.language/i18n.resolvedLanguagereturn region-specific tags likeen-USorde-DE, and what is the recommended way to handle base-language fallback (en, de)?

💡 Result:

Yes, i18n.language and i18n.resolvedLanguage can return region-specific tags like en-US or de-DE. i18n.language reflects the currently detected or set language code (e.g., from browser navigator or language detector), which can include region variants. i18n.resolvedLanguage (available since v21.0.0) represents the primary resolved language after applying configuration like supportedLngs and load options, suitable for use in language switchers. The recommended way to handle base-language fallback (e.g., en-US → en, de-DE → de) is through i18next configuration options: - Use load: 'all' (default): Loads and falls back from specific (en-US) to base (en) to fallbackLng. - Use supportedLngs: ['en', 'de'] with nonExplicitSupportedLngs: true: Treats variants like en-US as supported if base en is listed. - Use load: 'languageOnly': Strips to base language only (e.g., en), but may have bugs with supportedLngs as noted in recent issues. Provide translation resources for both specific (e.g., en-US/translation.json) and base (e.g., en/translation.json) languages. i18next automatically resolves keys by trying the most specific available match first. This applies identically in react-i18next, as it extends i18next. Access via useTranslation hook: const { i18n } = useTranslation; i18n.language or i18n.resolvedLanguage. Example configuration: i18next.init({ supportedLngs: ['en', 'de'], nonExplicitSupportedLngs: true, fallbackLng: 'en', load: 'all' // default });

Citations:


🏁 Script executed:

# First, let's examine the file in question
cat -n services/platform/app/features/chat/hooks/use-effective-agent.ts | head -70

Repository: tale-project/tale

Length of output: 3016


🏁 Script executed:

# Find where resolveAgentLocale is defined
fd -t f -e ts -e tsx | xargs grep -l "resolveAgentLocale" | head -10

Repository: tale-project/tale

Length of output: 184


🏁 Script executed:

# Look for the agent structure and i18n property
fd -t f -e ts -e tsx | xargs grep -l "agent\.i18n" | head -10

Repository: tale-project/tale

Length of output: 119


🏁 Script executed:

# Check i18next configuration
fd -t f -e ts -e tsx -e js | xargs grep -l "i18next.init\|i18n.init" | head -5

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Read the resolve-agent-locale implementation
cat -n services/platform/lib/shared/utils/resolve-agent-locale.ts

Repository: tale-project/tale

Length of output: 1574


🏁 Script executed:

# Search for i18next configuration in common locations
find services/platform -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) | xargs grep -l "i18next\.init\|i18n\.init\|i18n\.use" | head -10

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check what AgentI18nOverrides looks like
find services/platform -path "*/convex/agents/file_utils*" -type f | xargs cat

Repository: tale-project/tale

Length of output: 3705


🏁 Script executed:

# Search for how agent.i18n is populated or set
fd -t f -e ts -e tsx | xargs grep -A5 "i18n.*=" | grep -A5 "agent" | head -40

Repository: tale-project/tale

Length of output: 2381


🏁 Script executed:

# Look for test files or examples of agent.i18n usage
fd -t f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "*.test.tsx" \) | xargs grep -l "resolveAgentLocale\|agent.*i18n" 2>/dev/null

Repository: tale-project/tale

Length of output: 293


🏁 Script executed:

# Search for i18next configuration files
find services/platform -type f \( -name "i18n.ts" -o -name "i18next.ts" -o -name "i18n.config.ts" -o -name "main.ts" \) | head -10

Repository: tale-project/tale

Length of output: 132


🏁 Script executed:

# Look for where agent.i18n keys are actually populated/stored
rg "i18n\s*:\s*\{" -A 10 services/platform --type ts --type tsx | head -50

Repository: tale-project/tale

Length of output: 88


🏁 Script executed:

# Check if there are any examples of agent JSON files with i18n keys
find . -name "*.json" -path "*/agents/*" -type f | head -5 | xargs cat 2>/dev/null

Repository: tale-project/tale

Length of output: 24069


🏁 Script executed:

# Read the i18next configuration file
cat -n services/platform/lib/i18n/i18n.ts

Repository: tale-project/tale

Length of output: 906


🏁 Script executed:

# Check if there's any locale normalization already in place elsewhere
rg "split.*-|substring.*locale|\.language" services/platform/lib/i18n/ -A 3 -B 3

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Look for how agent.i18n keys are actually set/structured in real usage
rg "i18n.*\[" services/platform --type ts | head -20

Repository: tale-project/tale

Length of output: 466


🏁 Script executed:

# Check how locale is being set/detected - look for language detector setup
rg "languageDetector|LanguageDetector|browser" services/platform/lib/i18n/ -A 3

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check if there are other places where agent.i18n is accessed with different locale formats
rg "agent\.i18n\[|i18n\?\.\[" services/platform --type ts --type tsx -B 2 -A 2

Repository: tale-project/tale

Length of output: 88


🏁 Script executed:

# Verify what locales are actually expected/configured in the app
rg "defaultLocale\|supportedLngs\|fallbackLng" services/platform/lib/i18n/ -A 1

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check the i18n config file
cat -n services/platform/lib/i18n/config.ts

Repository: tale-project/tale

Length of output: 111


🏁 Script executed:

# Look for language detector configuration
rg "detect" services/platform/lib/i18n -i

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Search more broadly for how agents are being accessed with i18n
rg "agent\.i18n" services/platform -B 3 -A 3

Repository: tale-project/tale

Length of output: 662


🏁 Script executed:

# Check if there's any language detection or browser locale detection
find services/platform -type f -name "*.ts" -o -name "*.js" | xargs grep -l "navigator\.language\|languageDetector\|detect" 2>/dev/null | head -10

Repository: tale-project/tale

Length of output: 660


🏁 Script executed:

# Check the entire i18n directory for any other configuration
ls -la services/platform/lib/i18n/

Repository: tale-project/tale

Length of output: 466


🏁 Script executed:

# Verify if defaultLocale in parseDefaultLocale can be region-specific
rg "defaultLocale" services/platform --type ts -B 2 -A 2 | head -30

Repository: tale-project/tale

Length of output: 1933


🏁 Script executed:

# Check if there are any tests for resolveAgentLocale that show expected behavior
find services/platform -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "resolveAgentLocale" 2>/dev/null

Repository: tale-project/tale

Length of output: 45


🏁 Script executed:

# Look at the resolve-locale utility which seems to handle locale resolution
cat -n services/platform/lib/utils/intl/resolve-locale.ts

Repository: tale-project/tale

Length of output: 1144


🏁 Script executed:

# Check the i18n-provider and client setup
cat -n services/platform/lib/i18n/client.tsx
cat -n services/platform/lib/i18n/i18n-provider.tsx

Repository: tale-project/tale

Length of output: 781


🏁 Script executed:

# Search for actual usages in conversation-starters component to see how locales are being passed
cat -n services/platform/app/routes/dashboard/'$id'/agents/'$agentId'/conversation-starters.tsx | head -100

Repository: tale-project/tale

Length of output: 4087


🏁 Script executed:

# Check if there's a language detector or any code that sets language to region-specific values
rg "changeLanguage|setLanguage" services/platform --type ts -B 2 -A 2

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check SUPPORTED_AGENT_LOCALES to see what locales are actually supported
rg "SUPPORTED_AGENT_LOCALES" services/platform -A 5

Repository: tale-project/tale

Length of output: 3125


🏁 Script executed:

# Check if defaultLocale from organization metadata is validated to be base-only
rg "metadata.*locale\|defaultLocale" services/platform --type ts -B 2 -A 2 | head -40

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check if there's any dynamic language changing or language detection
rg "i18n\.changeLanguage|i18n\.use.*detect|LanguageDetector" services/platform

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Look at use-locale hook to see if it handles language locale detection
cat -n services/platform/app/hooks/use-locale.ts

Repository: tale-project/tale

Length of output: 2106


🏁 Script executed:

# Verify the complete flow - check if defaultLocale from metadata is validated
cat -n services/platform/lib/shared/constants/agents.ts | grep -A 10 "SUPPORTED_AGENT_LOCALES\|AgentLocale"

Repository: tale-project/tale

Length of output: 204


🏁 Script executed:

# Check if there's any place where i18n.changeLanguage is called
rg "changeLanguage|\.language\s*=" services/platform --type ts

Repository: tale-project/tale

Length of output: 165


🏁 Script executed:

# Verify that i18n instance is only used for translation keys, not language switching
rg "from.*react-i18next" services/platform --type ts -A 3 | head -50

Repository: tale-project/tale

Length of output: 1328


🏁 Script executed:

# Check if there's any test showing what i18n.language actually returns
find services/platform -name "*.test.ts" | xargs grep -l "i18n.language" 2>/dev/null

Repository: tale-project/tale

Length of output: 45


🏁 Script executed:

# Check if there are any integration tests that show what i18n.language actually returns
find services/platform -name "*.integration.ts" -o -name "*.e2e.ts" | xargs grep -l "i18n\|language" 2>/dev/null

Repository: tale-project/tale

Length of output: 45


🏁 Script executed:

# Look at the actual test for use-effective-agent to see what's being tested
find services/platform -path "*use-effective-agent*test*" -type f

Repository: tale-project/tale

Length of output: 140


🏁 Script executed:

# Check if there's any place where i18n configuration is modified or extended
rg "i18n\.init\|i18next\.init" services/platform --type ts -A 10

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Read the test file to understand expected behavior
cat -n services/platform/app/features/chat/hooks/__tests__/use-effective-agent.test.ts

Repository: tale-project/tale

Length of output: 5784


🏁 Script executed:

# Check if there's any dynamic language switching in the codebase
rg "localStorage.*user-locale\|changeLanguage" services/platform --type ts

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Final verification: Check if there's any recent change to i18n config or if language detector was added
git log --oneline -20 services/platform/lib/i18n/ 2>/dev/null || echo "No git history"

Repository: tale-project/tale

Length of output: 133


Consider normalizing locale tags to base language for forward compatibility.

While the current i18next configuration only loads 'en' and would return i18n.language as a base tag, the resolveAgentLocale function does exact-key matching against agent.i18n. If the i18n setup is later extended to support region-specific variants (e.g., 'de-DE') or a language detector is enabled, runtime locale tags could become region-specific while stored overrides remain base-only (e.g., 'de'), causing fallback failures.

🔧 Suggested fix
 const DEFAULT_CHAT_AGENT_NAME = 'chat-agent';
 const DEFAULT_LOCALE = 'en';
 
+function normalizeLocaleTag(locale: string): string {
+  return locale.split('-')[0]?.toLowerCase() || DEFAULT_LOCALE;
+}
+
 function parseDefaultLocale(metadata: unknown): string {
   if (metadata && typeof metadata === 'object' && 'defaultLocale' in metadata) {
     const value = (metadata as { defaultLocale: unknown }).defaultLocale; // oxlint-disable-line typescript/no-unsafe-type-assertion
     if (typeof value === 'string') return value;
   }
   return DEFAULT_LOCALE;
 }
@@
-  const locale = i18n.language;
-  const defaultLocale = parseDefaultLocale(organization?.metadata);
+  const locale = normalizeLocaleTag(i18n.language);
+  const defaultLocale = normalizeLocaleTag(
+    parseDefaultLocale(organization?.metadata),
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/hooks/use-effective-agent.ts` around
lines 44 - 50, The resolve function should normalize runtime locale tags to
their base language before calling resolveAgentLocale to avoid mismatches with
base-only agent.i18n keys; update the code in use-effective-agent.ts (inside the
resolve function where locale and defaultLocale are used) to derive baseLocale
(e.g., strip region subtags like "de-DE" -> "de") and baseDefaultLocale and pass
those normalized values into resolveAgentLocale(agent, baseLocale,
baseDefaultLocale) so that agents (from agents and agent.i18n) match regardless
of region-specific runtime tags.

Comment on lines 90 to 94
defaultLocale:
typeof existingMetadata.defaultLocale === 'string'
? existingMetadata.defaultLocale
: DEFAULT_LOCALE,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider validating that stored defaultLocale is a supported value.

The form initializes defaultLocale from existingMetadata.defaultLocale if it's a string, but doesn't verify it's in SUPPORTED_AGENT_LOCALES. If legacy data contains an unsupported locale (e.g., 'fr'), it will be used and re-persisted.

This could cause unexpected behavior in resolveAgentLocale() which would attempt to look up i18n overrides for a locale that doesn't exist in the agent's i18n record.

🛡️ Proposed fix to validate against supported locales
+import type { AgentLocale } from '@/lib/shared/constants/agents';
+
+function isValidLocale(value: string): value is AgentLocale {
+  return (SUPPORTED_AGENT_LOCALES as readonly string[]).includes(value);
+}
+
 const form = useForm<OrganizationFormData>({
   mode: 'onChange',
   defaultValues: {
     name: organization?.name || '',
     defaultLocale:
-      typeof existingMetadata.defaultLocale === 'string'
+      typeof existingMetadata.defaultLocale === 'string' &&
+      isValidLocale(existingMetadata.defaultLocale)
         ? existingMetadata.defaultLocale
         : DEFAULT_LOCALE,
   },
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/settings/organization/components/organization-settings.tsx`
around lines 90 - 94, The form currently seeds defaultLocale from
existingMetadata.defaultLocale without checking it is one of
SUPPORTED_AGENT_LOCALES, so validate that value before using it: when
initializing defaultLocale (the block referencing existingMetadata.defaultLocale
and DEFAULT_LOCALE) check membership in SUPPORTED_AGENT_LOCALES and fall back to
DEFAULT_LOCALE if not supported; also ensure any save/update path that persists
defaultLocale performs the same validation to avoid re-persisting unsupported
legacy locales (this will prevent resolveAgentLocale() from looking up missing
keys in the agent i18n record).

Comment on lines +54 to +62
const DEFAULT_LOCALE = 'en';

function parseDefaultLocale(metadata: unknown): string {
if (metadata && typeof metadata === 'object' && 'defaultLocale' in metadata) {
const value = (metadata as { defaultLocale: unknown }).defaultLocale; // oxlint-disable-line typescript/no-unsafe-type-assertion
if (typeof value === 'string') return value;
}
return DEFAULT_LOCALE;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Duplicate parseDefaultLocale function.

This function is identical to the one in services/platform/app/features/chat/hooks/use-effective-agent.ts (see context snippet 2). Consider extracting to a shared utility to avoid duplication.

♻️ Suggested refactor

Extract to a shared utility, e.g., lib/shared/utils/parse-default-locale.ts:

const DEFAULT_LOCALE = 'en';

export function parseDefaultLocale(metadata: unknown): string {
  if (metadata && typeof metadata === 'object' && 'defaultLocale' in metadata) {
    const value = (metadata as { defaultLocale: unknown }).defaultLocale;
    if (typeof value === 'string') return value;
  }
  return DEFAULT_LOCALE;
}

Then import in both use-effective-agent.ts and conversation-starters.tsx.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/routes/dashboard/`$id/agents/$agentId/conversation-starters.tsx
around lines 54 - 62, The duplicate parseDefaultLocale function (and its
DEFAULT_LOCALE constant) should be extracted into a single shared utility
module: move the logic into an exported function parseDefaultLocale and export
DEFAULT_LOCALE from a common utility, then replace the local implementations in
both conversation-starters.tsx and use-effective-agent.ts with imports from that
utility; ensure the exported function signature and behavior remain identical
and update imports where parseDefaultLocale and DEFAULT_LOCALE are referenced.

Comment on lines +89 to +94
function getStarters(): string[] {
if (editingLocale === null) {
return config.conversationStarters ?? [];
}
return config.i18n?.[editingLocale]?.conversationStarters ?? [];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

getStarters function is redefined on every render.

This closure is recreated on each render since it's a plain function inside the component. While it works correctly, consider memoizing or restructuring for clarity.

♻️ Suggested refactor using useMemo
-  function getStarters(): string[] {
-    if (editingLocale === null) {
-      return config.conversationStarters ?? [];
-    }
-    return config.i18n?.[editingLocale]?.conversationStarters ?? [];
-  }
+  const currentStarters = useMemo(() => {
+    if (editingLocale === null) {
+      return config.conversationStarters ?? [];
+    }
+    return config.i18n?.[editingLocale]?.conversationStarters ?? [];
+  }, [editingLocale, config.conversationStarters, config.i18n]);

-  const [items, setItems] = useState<StarterItem[]>(() =>
-    toItems(getStarters()),
-  );
+  const [items, setItems] = useState<StarterItem[]>(() =>
+    toItems(config.conversationStarters ?? []),
+  );

-  const startersKey = JSON.stringify(getStarters());
+  const startersKey = JSON.stringify(currentStarters);
   useEffect(() => {
-    setItems(toItems(getStarters()));
+    setItems(toItems(currentStarters));
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [startersKey, editingLocale]);
+  }, [startersKey]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/routes/dashboard/`$id/agents/$agentId/conversation-starters.tsx
around lines 89 - 94, getStarters is recreated on every render because it's
declared inside the component; memoize it with React.useMemo (depend on
editingLocale and config) or move the logic into a stable helper to avoid
redefinition. Specifically, wrap the logic that reads
config.conversationStarters and
config.i18n?.[editingLocale]?.conversationStarters in useMemo (dependencies:
[editingLocale, config]) so getStarters returns the memoized array and is not
recreated each render.

Comment on lines +234 to +267
<div className="scrollbar-hide border-border flex items-center gap-4 overflow-x-auto border-b">
{localeTabs.map(({ locale, isDefault }) => {
const active = isDefault
? editingLocale === null
: editingLocale === locale;
return (
<button
key={locale}
type="button"
onClick={() => setEditingLocale(isDefault ? null : locale)}
className={cn(
'relative flex shrink-0 items-center gap-1.5 whitespace-nowrap pb-2 text-sm font-medium transition-colors',
active
? 'text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
{tGlobal(`languages.${locale}`)}
{isDefault && (
<span className="text-muted-foreground text-xs">
({t('agents.conversationStarters.default')})
</span>
)}
{!isDefault && !hasLocaleContent(locale) && (
<span className="bg-muted text-muted-foreground rounded px-1 py-0.5 text-[10px] leading-none">
{t('agents.conversationStarters.untranslated')}
</span>
)}
{active && (
<span className="bg-foreground absolute bottom-0 left-0 h-0.5 w-full" />
)}
</button>
);
})}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tab buttons lack visible focus states for keyboard accessibility.

The locale tab buttons use custom styling but don't include explicit focus-visible styles. Per coding guidelines, interactive elements should have visible focus states.

♿ Proposed fix to add focus states
             className={cn(
               'relative flex shrink-0 items-center gap-1.5 whitespace-nowrap pb-2 text-sm font-medium transition-colors',
+              'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
               active
                 ? 'text-foreground'
                 : 'text-muted-foreground hover:text-foreground',
             )}

As per coding guidelines: "Ensure all interactive elements are keyboard accessible and have visible focus states."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/routes/dashboard/`$id/agents/$agentId/conversation-starters.tsx
around lines 234 - 267, The locale tab buttons rendered in the localeTabs map
(button with onClick calling setEditingLocale, key={locale}) lack an explicit
focus-visible style; update the className passed into cn for that button to
include keyboard-focus styles (e.g., focus:outline-none and focus-visible:ring /
focus-visible:ring-offset / focus-visible:ring-foreground with an accessible
ring color and offset) so the active/focus state is visible for keyboard users
while preserving the existing active/hover classes; ensure the focus rules apply
to the same button element that uses hasLocaleContent, tGlobal and t so keyboard
users can clearly see which tab is focused.

export const MAX_CONVERSATION_STARTER_LENGTH = 200;

export const SUPPORTED_AGENT_LOCALES = ['en', 'de'] as const;
export type AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove or consume unused exported type AgentLocale (lint blocker).

Line 5 introduces an exported type that KNIP flags as unused, currently failing CI.

🔧 Minimal fix
 export const SUPPORTED_AGENT_LOCALES = ['en', 'de'] as const;
-export type AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];
+type AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];
type AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];
🧰 Tools
🪛 GitHub Actions: Lint

[error] 5-5: KNIP found unused exported type 'AgentLocale'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/lib/shared/constants/agents.ts` at line 5, The exported
type AgentLocale is unused and causing a lint failure; either remove the unused
export or consume it where appropriate. Locate the declaration "export type
AgentLocale = (typeof SUPPORTED_AGENT_LOCALES)[number];" and either (a) delete
the export (or the entire type line) if no code references it, or (b) replace
the unused export with an internal (non-exported) type or use AgentLocale in the
code that defines or types agent-related functions/variables (e.g., function
signatures, params, or constants referencing SUPPORTED_AGENT_LOCALES) so it is
consumed; ensure the change removes the unused-export lint error without
altering runtime behavior.

conversationStarters: z.array(z.string().max(200)).max(4).optional(),
});

export type TranslatableFields = z.infer<typeof translatableFieldsSchema>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

TranslatableFields export is currently unused and breaking lint.

Line 25 exports a type KNIP marks as unused; this is currently a failing check.

🔧 Minimal fix
-export type TranslatableFields = z.infer<typeof translatableFieldsSchema>;
+type TranslatableFields = z.infer<typeof translatableFieldsSchema>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TranslatableFields = z.infer<typeof translatableFieldsSchema>;
type TranslatableFields = z.infer<typeof translatableFieldsSchema>;
🧰 Tools
🪛 GitHub Actions: Lint

[error] 25-25: KNIP found unused exported type 'TranslatableFields'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/lib/shared/schemas/agents.ts` at line 25, Remove the unused
exported type TranslatableFields to satisfy the linter: delete the line
exporting TranslatableFields (which references translatableFieldsSchema) or, if
the type will be needed internally only, convert it to a non-exported type
(e.g., change "export type TranslatableFields" to "type TranslatableFields") so
the symbol is no longer exported and the unused-export lint error is resolved.

larryro added 2 commits April 2, 2026 15:45
- Refactor translateFields to use generateObject with Zod schema and
  3-retry loop instead of fragile generateText + JSON.parse
- Make translateFields generic (Record<string, string | string[]>) so
  it can translate any combination of agent fields in one call
- Extract duplicated parseDefaultLocale into shared utility
- Fix race condition: disable locale tabs during auto-translation and
  guard against stale locale after async completion
- Add console.error to catch block per project coding standards
- Fix broken tests by adding mocks for useTranslation and useOrganization
- Add i18n locale resolution test cases and resolveAgentLocale unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant