Skip to content

Commit bbfe400

Browse files
authored
🏷️ chore: Add Missing Localizations for Agents, Categories, Bookmarks (#9266)
* fix: error when updating bookmarks if no query data * feat: localize bookmark dialog, form labels and validation messages, also improve validation * feat: add localization for EmptyPromptPreview component and update translation.json * chore: add missing localizations for static UI text * chore: update AgentPanelContextType and useGetAgentsConfig to support null configurations * refactor: update agent categories to support localization and custom properties, improve related typing * ci: add localization for 'All' category and update tab names in accessibility tests * chore: remove unused AgentCategoryDisplay component and its tests * chore: add localization handling for agent category selector * chore: enhance AgentCard to support localized category labels and add related tests * chore: enhance i18n unused keys detection to include additional source directories and improve handling for agent category keys
1 parent 94426a3 commit bbfe400

File tree

22 files changed

+328
-252
lines changed

22 files changed

+328
-252
lines changed

.github/workflows/i18n-unused-keys.yml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
name: Detect Unused i18next Strings
22

3+
# This workflow checks for unused i18n keys in translation files.
4+
# It has special handling for:
5+
# - com_ui_special_var_* keys that are dynamically constructed
6+
# - com_agents_category_* keys that are stored in the database and used dynamically
7+
38
on:
49
pull_request:
510
paths:
611
- "client/src/**"
712
- "api/**"
813
- "packages/data-provider/src/**"
914
- "packages/client/**"
15+
- "packages/data-schemas/src/**"
1016

1117
jobs:
1218
detect-unused-i18n-keys:
@@ -24,7 +30,7 @@ jobs:
2430
2531
# Define paths
2632
I18N_FILE="client/src/locales/en/translation.json"
27-
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client")
33+
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client" "packages/data-schemas/src")
2834
2935
# Check if translation file exists
3036
if [[ ! -f "$I18N_FILE" ]]; then
@@ -52,6 +58,31 @@ jobs:
5258
fi
5359
done
5460
61+
# Also check if the key is directly used somewhere
62+
if [[ "$FOUND" == false ]]; then
63+
for DIR in "${SOURCE_DIRS[@]}"; do
64+
if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
65+
FOUND=true
66+
break
67+
fi
68+
done
69+
fi
70+
# Special case for agent category keys that are dynamically used from database
71+
elif [[ "$KEY" == com_agents_category_* ]]; then
72+
# Check if agent category localization is being used
73+
for DIR in "${SOURCE_DIRS[@]}"; do
74+
# Check for dynamic category label/description usage
75+
if grep -r --include=\*.{js,jsx,ts,tsx} -E "category\.(label|description).*startsWith.*['\"]com_" "$DIR" > /dev/null 2>&1 || \
76+
# Check for the method that defines these keys
77+
grep -r --include=\*.{js,jsx,ts,tsx} "ensureDefaultCategories" "$DIR" > /dev/null 2>&1 || \
78+
# Check for direct usage in agentCategory.ts
79+
grep -r --include=\*.ts -E "label:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1 || \
80+
grep -r --include=\*.ts -E "description:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1; then
81+
FOUND=true
82+
break
83+
fi
84+
done
85+
5586
# Also check if the key is directly used somewhere
5687
if [[ "$FOUND" == false ]]; then
5788
for DIR in "${SOURCE_DIRS[@]}"; do

api/server/routes/agents/v1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ router.use('/tools', tools);
4646

4747
/**
4848
* Get all agent categories with counts
49-
* @route GET /agents/marketplace/categories
49+
* @route GET /agents/categories
5050
*/
5151
router.get('/categories', v1.getAgentCategories);
5252
/**

client/src/common/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ export type AgentPanelContextType = {
225225
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
226226
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
227227
agent_id?: string;
228-
agentsConfig?: t.TAgentsEndpoint;
228+
agentsConfig?: t.TAgentsEndpoint | null;
229+
endpointsConfig?: t.TEndpointsConfig | null;
229230
};
230231

231232
export type AgentModelPanelProps = {

client/src/components/Agents/AgentCard.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import { Label } from '@librechat/client';
33
import type t from 'librechat-data-provider';
4+
import { useLocalize, TranslationKeys, useAgentCategories } from '~/hooks';
45
import { cn, renderAgentAvatar, getContactDisplayName } from '~/utils';
5-
import { useLocalize } from '~/hooks';
66

77
interface AgentCardProps {
88
agent: t.Agent; // The agent data to display
@@ -15,6 +15,21 @@ interface AgentCardProps {
1515
*/
1616
const AgentCard: React.FC<AgentCardProps> = ({ agent, onClick, className = '' }) => {
1717
const localize = useLocalize();
18+
const { categories } = useAgentCategories();
19+
20+
const categoryLabel = useMemo(() => {
21+
if (!agent.category) return '';
22+
23+
const category = categories.find((cat) => cat.value === agent.category);
24+
if (category) {
25+
if (category.label && category.label.startsWith('com_')) {
26+
return localize(category.label as TranslationKeys);
27+
}
28+
return category.label;
29+
}
30+
31+
return agent.category.charAt(0).toUpperCase() + agent.category.slice(1);
32+
}, [agent.category, categories, localize]);
1833

1934
return (
2035
<div
@@ -49,9 +64,7 @@ const AgentCard: React.FC<AgentCardProps> = ({ agent, onClick, className = '' })
4964
{/* Category tag */}
5065
{agent.category && (
5166
<div className="inline-flex items-center rounded-md border-border-xheavy bg-surface-active-alt px-2 py-1 text-xs font-medium">
52-
<Label className="line-clamp-1 font-normal">
53-
{agent.category.charAt(0).toUpperCase() + agent.category.slice(1)}
54-
</Label>
67+
<Label className="line-clamp-1 font-normal">{categoryLabel}</Label>
5568
</div>
5669
)}
5770
</div>

client/src/components/Agents/AgentCategoryDisplay.tsx

Lines changed: 0 additions & 62 deletions
This file was deleted.

client/src/components/Agents/CategoryTabs.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
2-
import type t from 'librechat-data-provider';
32
import { useMediaQuery } from '@librechat/client';
3+
import type t from 'librechat-data-provider';
4+
import { useLocalize, TranslationKeys } from '~/hooks';
45
import { SmartLoader } from './SmartLoader';
5-
import { useLocalize } from '~/hooks/';
66
import { cn } from '~/utils';
77

88
/**
@@ -36,14 +36,17 @@ const CategoryTabs: React.FC<CategoryTabsProps> = ({
3636
const localize = useLocalize();
3737
const isSmallScreen = useMediaQuery('(max-width: 768px)');
3838

39-
// Helper function to get category display name from database data
39+
/** Helper function to get category display name from database data */
4040
const getCategoryDisplayName = (category: t.TCategory) => {
4141
// Special cases for system categories
4242
if (category.value === 'promoted') {
4343
return localize('com_agents_top_picks');
4444
}
4545
if (category.value === 'all') {
46-
return 'All';
46+
return localize('com_agents_all_category');
47+
}
48+
if (category.label && category.label.startsWith('com_')) {
49+
return localize(category.label as TranslationKeys);
4750
}
4851
// Use database label or fallback to capitalized value
4952
return category.label || category.value.charAt(0).toUpperCase() + category.value.slice(1);
@@ -158,7 +161,11 @@ const CategoryTabs: React.FC<CategoryTabsProps> = ({
158161
aria-selected={activeTab === category.value}
159162
aria-controls={`tabpanel-${category.value}`}
160163
tabIndex={activeTab === category.value ? 0 : -1}
161-
aria-label={`${getCategoryDisplayName(category)} tab (${index + 1} of ${categories.length})`}
164+
aria-label={localize('com_agents_category_tab_label', {
165+
category: getCategoryDisplayName(category),
166+
position: index + 1,
167+
total: categories.length,
168+
})}
162169
>
163170
{getCategoryDisplayName(category)}
164171
{/* Underline for active tab */}

client/src/components/Agents/Marketplace.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { TooltipAnchor, Button, NewChatIcon, useMediaQuery } from '@librechat/cl
77
import { PermissionTypes, Permissions, QueryKeys, Constants } from 'librechat-data-provider';
88
import type t from 'librechat-data-provider';
99
import type { ContextType } from '~/common';
10+
import { useDocumentTitle, useHasAccess, useLocalize, TranslationKeys } from '~/hooks';
1011
import { useGetEndpointsQuery, useGetAgentCategoriesQuery } from '~/data-provider';
11-
import { useDocumentTitle, useHasAccess, useLocalize } from '~/hooks';
1212
import MarketplaceAdminSettings from './MarketplaceAdminSettings';
1313
import { SidePanelProvider, useChatContext } from '~/Providers';
1414
import { MarketplaceProvider } from './MarketplaceContext';
@@ -381,8 +381,8 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
381381
}
382382
if (displayCategory === 'all') {
383383
return {
384-
name: 'All Agents',
385-
description: 'Browse all shared agents across all categories',
384+
name: localize('com_agents_all'),
385+
description: localize('com_agents_all_description'),
386386
};
387387
}
388388

@@ -392,8 +392,12 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
392392
);
393393
if (categoryData) {
394394
return {
395-
name: categoryData.label,
396-
description: categoryData.description || '',
395+
name: categoryData.label?.startsWith('com_')
396+
? localize(categoryData.label as TranslationKeys)
397+
: categoryData.label,
398+
description: categoryData.description?.startsWith('com_')
399+
? localize(categoryData.description as TranslationKeys)
400+
: categoryData.description || '',
397401
};
398402
}
399403

@@ -455,8 +459,8 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
455459
}
456460
if (nextCategory === 'all') {
457461
return {
458-
name: 'All Agents',
459-
description: 'Browse all shared agents across all categories',
462+
name: localize('com_agents_all'),
463+
description: localize('com_agents_all_description'),
460464
};
461465
}
462466

@@ -466,8 +470,16 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
466470
);
467471
if (categoryData) {
468472
return {
469-
name: categoryData.label,
470-
description: categoryData.description || '',
473+
name: categoryData.label?.startsWith('com_')
474+
? localize(categoryData.label as TranslationKeys)
475+
: categoryData.label,
476+
description: categoryData.description?.startsWith('com_')
477+
? localize(
478+
categoryData.description as Parameters<
479+
typeof localize
480+
>[0],
481+
)
482+
: categoryData.description || '',
471483
};
472484
}
473485

client/src/components/Agents/tests/Accessibility.spec.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const mockLocalize = jest.fn((key: string, options?: any) => {
6161
com_agents_search_empty_heading: 'No search results',
6262
com_agents_created_by: 'by',
6363
com_agents_top_picks: 'Top Picks',
64+
com_agents_all_category: 'All',
6465
// ErrorDisplay translations
6566
com_agents_error_suggestion_generic: 'Try refreshing the page or check your network connection',
6667
com_agents_error_network_title: 'Network Error',
@@ -199,7 +200,7 @@ describe('Accessibility Improvements', () => {
199200
/>,
200201
);
201202

202-
const promotedTab = screen.getByRole('tab', { name: /Top Picks tab/ });
203+
const promotedTab = screen.getByRole('tab', { name: /Top Picks category/ });
203204

204205
// Test arrow key navigation
205206
fireEvent.keyDown(promotedTab, { key: 'ArrowRight' });
@@ -226,8 +227,8 @@ describe('Accessibility Improvements', () => {
226227
/>,
227228
);
228229

229-
const promotedTab = screen.getByRole('tab', { name: /Top Picks tab/ });
230-
const allTab = screen.getByRole('tab', { name: /All tab/ });
230+
const promotedTab = screen.getByRole('tab', { name: /Top Picks category/ });
231+
const allTab = screen.getByRole('tab', { name: /All category/ });
231232

232233
// Active tab should be focusable
233234
expect(promotedTab).toHaveAttribute('tabIndex', '0');

0 commit comments

Comments
 (0)