Skip to content

Commit 75ee112

Browse files
jtpiobrichet
andauthored
Localize user facing strings (#238)
* Localize user facing strings * Apply suggestions from code review Co-authored-by: Nicolas Brichet <[email protected]> * fix typings --------- Co-authored-by: Nicolas Brichet <[email protected]>
1 parent a91e9f7 commit 75ee112

File tree

11 files changed

+409
-186
lines changed

11 files changed

+409
-186
lines changed

src/approval-buttons.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { ChatWidget } from '@jupyter/chat';
22
import { IDisposable } from '@lumino/disposable';
3+
import type { TranslationBundle } from '@jupyterlab/translation';
34
import { AIChatModel } from './chat-model';
45

56
export class ApprovalButtons implements IDisposable {
67
constructor(options: ApprovalButtons.IOptions) {
78
this._chatPanel = options.chatPanel;
89
this._chatModel = this._chatPanel.model as AIChatModel;
10+
this._trans = options.trans;
911

1012
// Set up approval button event handling
1113
this._setupApprovalHandlers();
@@ -201,7 +203,9 @@ export class ApprovalButtons implements IDisposable {
201203
icon.textContent = isApprove ? '✅' : '❌';
202204

203205
const text = document.createElement('span');
204-
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
206+
text.textContent = isApprove
207+
? this._trans.__('Tools approved')
208+
: this._trans.__('Tools rejected');
205209

206210
statusDiv.appendChild(icon);
207211
statusDiv.appendChild(text);
@@ -230,7 +234,9 @@ export class ApprovalButtons implements IDisposable {
230234
icon.textContent = isApprove ? '✅' : '❌';
231235

232236
const text = document.createElement('span');
233-
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
237+
text.textContent = isApprove
238+
? this._trans.__('Tools approved')
239+
: this._trans.__('Tools rejected');
234240

235241
statusDiv.appendChild(icon);
236242
statusDiv.appendChild(text);
@@ -346,8 +352,14 @@ export class ApprovalButtons implements IDisposable {
346352
buttonContainer.setAttribute('data-message-id', messageId);
347353
}
348354

349-
const approveBtn = this._createApprovalButton('Approve', true);
350-
const rejectBtn = this._createApprovalButton('Reject', false);
355+
const approveBtn = this._createApprovalButton(
356+
this._trans.__('Approve'),
357+
true
358+
);
359+
const rejectBtn = this._createApprovalButton(
360+
this._trans.__('Reject'),
361+
false
362+
);
351363

352364
// Add click handlers directly to the buttons
353365
this._addButtonHandler(approveBtn);
@@ -388,12 +400,12 @@ export class ApprovalButtons implements IDisposable {
388400
}
389401

390402
const approveBtn = this._createApprovalButton(
391-
'Approve',
403+
this._trans.__('Approve'),
392404
true,
393405
'jp-ai-group-approve-all'
394406
);
395407
const rejectBtn = this._createApprovalButton(
396-
'Reject',
408+
this._trans.__('Reject'),
397409
false,
398410
'jp-ai-group-reject-all'
399411
);
@@ -443,6 +455,7 @@ export class ApprovalButtons implements IDisposable {
443455
private _chatModel: AIChatModel;
444456
private _isDisposed: boolean = false;
445457
private _mutationObserver?: MutationObserver;
458+
private _trans: TranslationBundle;
446459
}
447460

448461
/**
@@ -457,5 +470,9 @@ export namespace ApprovalButtons {
457470
* The chat panel widget to wrap.
458471
*/
459472
chatPanel: ChatWidget;
473+
/**
474+
* The application language translation bundle.
475+
*/
476+
trans: TranslationBundle;
460477
}
461478
}

src/components/clear-button.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
22

3+
import type { TranslationBundle } from '@jupyterlab/translation';
4+
35
import ClearIcon from '@mui/icons-material/Clear';
46

57
import React from 'react';
@@ -15,13 +17,18 @@ export interface IClearButtonProps
1517
* The function to clear messages.
1618
*/
1719
clearMessages: () => void;
20+
/**
21+
* The application language translator.
22+
*/
23+
translator: TranslationBundle;
1824
}
1925

2026
/**
2127
* The clear button component.
2228
*/
2329
export function ClearButton(props: IClearButtonProps): JSX.Element {
24-
const tooltip = 'Clear chat';
30+
const { translator: trans } = props;
31+
const tooltip = trans.__('Clear chat');
2532
return (
2633
<TooltippedButton
2734
onClick={props.clearMessages}
@@ -41,13 +48,19 @@ export function ClearButton(props: IClearButtonProps): JSX.Element {
4148
/**
4249
* Factory returning the clear button toolbar item.
4350
*/
44-
export function clearItem(): InputToolbarRegistry.IToolbarItem {
51+
export function clearItem(
52+
translator: TranslationBundle
53+
): InputToolbarRegistry.IToolbarItem {
4554
return {
4655
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
4756
const { model } = props;
4857
const clearMessages = () =>
4958
(model.chatContext as AIChatModel.IAIChatContext).clearMessages();
50-
const clearProps: IClearButtonProps = { ...props, clearMessages };
59+
const clearProps: IClearButtonProps = {
60+
...props,
61+
clearMessages,
62+
translator
63+
};
5164
return ClearButton(clearProps);
5265
},
5366
position: 0,

src/components/completion-status.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect, useState } from 'react';
22
import { AISettingsModel } from '../models/settings-model';
33
import { ReactWidget } from '@jupyterlab/ui-components';
4+
import type { TranslationBundle } from '@jupyterlab/translation';
45
import { jupyternautIcon } from '../icons';
56

67
const COMPLETION_STATUS_CLASS = 'jp-ai-completion-status';
@@ -14,12 +15,17 @@ interface ICompletionStatusProps {
1415
* The settings model.
1516
*/
1617
settingsModel: AISettingsModel;
18+
/**
19+
* The application language translator.
20+
*/
21+
translator: TranslationBundle;
1722
}
1823

1924
/**
2025
* The completion status component.
2126
*/
2227
function CompletionStatus(props: ICompletionStatusProps): JSX.Element {
28+
const { translator: trans } = props;
2329
const [disabled, setDisabled] = useState<boolean>(true);
2430
const [title, setTitle] = useState<string>('');
2531

@@ -30,15 +36,23 @@ function CompletionStatus(props: ICompletionStatusProps): JSX.Element {
3036
const stateChanged = (model: AISettingsModel) => {
3137
if (model.config.useSameProviderForChatAndCompleter) {
3238
setDisabled(false);
33-
setTitle(`Completion using ${model.getDefaultProvider()?.model}`);
39+
setTitle(
40+
trans.__(
41+
'Completion using %1',
42+
model.getDefaultProvider()?.model ?? ''
43+
)
44+
);
3445
} else if (model.config.activeCompleterProvider) {
3546
setDisabled(false);
3647
setTitle(
37-
`Completion using ${model.getProvider(model.config.activeCompleterProvider)?.model}`
48+
trans.__(
49+
'Completion using %1',
50+
model.getProvider(model.config.activeCompleterProvider)?.model ?? ''
51+
)
3852
);
3953
} else {
4054
setDisabled(true);
41-
setTitle('No completion');
55+
setTitle(trans.__('No completion'));
4256
}
4357
};
4458

@@ -48,7 +62,7 @@ function CompletionStatus(props: ICompletionStatusProps): JSX.Element {
4862
return () => {
4963
props.settingsModel.stateChanged.disconnect(stateChanged);
5064
};
51-
}, [props.settingsModel]);
65+
}, [props.settingsModel, trans]);
5266

5367
return (
5468
<jupyternautIcon.react

src/components/model-select.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
2+
import type { TranslationBundle } from '@jupyterlab/translation';
23
import CheckIcon from '@mui/icons-material/Check';
34
import { Menu, MenuItem, Typography } from '@mui/material';
45
import React, { useCallback, useEffect, useState } from 'react';
@@ -14,13 +15,17 @@ export interface IModelSelectProps
1415
* The settings model to get available models and current selection from.
1516
*/
1617
settingsModel: AISettingsModel;
18+
/**
19+
* The application language translator.
20+
*/
21+
translator: TranslationBundle;
1722
}
1823

1924
/**
2025
* The model select component for choosing AI models.
2126
*/
2227
export function ModelSelect(props: IModelSelectProps): JSX.Element {
23-
const { settingsModel, model } = props;
28+
const { settingsModel, model, translator: trans } = props;
2429
const agentManager = (model.chatContext as AIChatModel.IAIChatContext)
2530
.agentManager;
2631

@@ -97,13 +102,15 @@ export function ModelSelect(props: IModelSelectProps): JSX.Element {
97102
return (
98103
<TooltippedButton
99104
onClick={() => {}}
100-
tooltip="No providers configured. Please go to AI Settings to add a provider."
105+
tooltip={trans.__(
106+
'No providers configured. Please go to AI Settings to add a provider.'
107+
)}
101108
buttonProps={{
102109
size: 'small',
103110
variant: 'outlined',
104111
color: 'warning',
105112
disabled: true,
106-
title: 'No Providers Available'
113+
title: trans.__('No Providers Available')
107114
}}
108115
sx={{
109116
minWidth: 'auto',
@@ -116,7 +123,7 @@ export function ModelSelect(props: IModelSelectProps): JSX.Element {
116123
variant="caption"
117124
sx={{ fontSize: '0.7rem', fontWeight: 500 }}
118125
>
119-
No Providers
126+
{trans.__('No Providers')}
120127
</Typography>
121128
</TooltippedButton>
122129
);
@@ -128,12 +135,16 @@ export function ModelSelect(props: IModelSelectProps): JSX.Element {
128135
onClick={e => {
129136
openMenu(e.currentTarget);
130137
}}
131-
tooltip={`Current Model: ${currentProviderLabel} - ${currentModel}`}
138+
tooltip={trans.__(
139+
'Current Model: %1 - %2',
140+
currentProviderLabel,
141+
currentModel
142+
)}
132143
buttonProps={{
133144
size: 'small',
134145
variant: 'contained',
135146
color: 'primary',
136-
title: 'Select AI Model',
147+
title: trans.__('Select AI Model'),
137148
onKeyDown: e => {
138149
if (e.key !== 'Enter' && e.key !== ' ') {
139150
return;
@@ -237,7 +248,8 @@ export function ModelSelect(props: IModelSelectProps): JSX.Element {
237248
* Factory function returning the toolbar item for model selection.
238249
*/
239250
export function createModelSelectItem(
240-
settingsModel: AISettingsModel
251+
settingsModel: AISettingsModel,
252+
translator: TranslationBundle
241253
): InputToolbarRegistry.IToolbarItem {
242254
return {
243255
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
@@ -247,7 +259,8 @@ export function createModelSelectItem(
247259
}
248260
const modelSelectProps: IModelSelectProps = {
249261
...props,
250-
settingsModel
262+
settingsModel,
263+
translator
251264
};
252265
return <ModelSelect {...modelSelectProps} />;
253266
},

src/components/stop-button.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
22

3+
import type { TranslationBundle } from '@jupyterlab/translation';
4+
35
import StopIcon from '@mui/icons-material/Stop';
46

57
import React from 'react';
@@ -15,13 +17,18 @@ export interface IStopButtonProps
1517
* The function to stop streaming.
1618
*/
1719
stopStreaming: () => void;
20+
/**
21+
* The application language translator.
22+
*/
23+
translator: TranslationBundle;
1824
}
1925

2026
/**
2127
* The stop button component.
2228
*/
2329
export function StopButton(props: IStopButtonProps): JSX.Element {
24-
const tooltip = 'Stop streaming';
30+
const { translator: trans } = props;
31+
const tooltip = trans.__('Stop streaming');
2532
return (
2633
<TooltippedButton
2734
onClick={props.stopStreaming}
@@ -41,13 +48,19 @@ export function StopButton(props: IStopButtonProps): JSX.Element {
4148
/**
4249
* Factory returning the stop button toolbar item.
4350
*/
44-
export function stopItem(): InputToolbarRegistry.IToolbarItem {
51+
export function stopItem(
52+
translator: TranslationBundle
53+
): InputToolbarRegistry.IToolbarItem {
4554
return {
4655
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
4756
const { model } = props;
4857
const stopStreaming = () =>
4958
(model.chatContext as AIChatModel.IAIChatContext).stopStreaming();
50-
const stopProps: IStopButtonProps = { ...props, stopStreaming };
59+
const stopProps: IStopButtonProps = {
60+
...props,
61+
stopStreaming,
62+
translator
63+
};
5164
return StopButton(stopProps);
5265
},
5366
position: 50,

src/components/token-usage-display.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
2+
import type { TranslationBundle } from '@jupyterlab/translation';
23
import React from 'react';
34
import { ISignal } from '@lumino/signaling';
45
import { AISettingsModel } from '../models/settings-model';
@@ -22,6 +23,11 @@ export interface ITokenUsageDisplayProps {
2223
* Initial token usage.
2324
*/
2425
initialTokenUsage?: ITokenUsage;
26+
27+
/**
28+
* The application language translator.
29+
*/
30+
translator: TranslationBundle;
2531
}
2632

2733
/**
@@ -32,7 +38,8 @@ export interface ITokenUsageDisplayProps {
3238
export const TokenUsageDisplay: React.FC<ITokenUsageDisplayProps> = ({
3339
tokenUsageChanged,
3440
settingsModel,
35-
initialTokenUsage
41+
initialTokenUsage,
42+
translator: trans
3643
}) => {
3744
return (
3845
<UseSignal signal={settingsModel.stateChanged} initialArgs={undefined}>
@@ -68,7 +75,12 @@ export const TokenUsageDisplay: React.FC<ITokenUsageDisplayProps> = ({
6875
borderRadius: '4px',
6976
whiteSpace: 'nowrap'
7077
}}
71-
title={`Token Usage - Sent: ${tokenUsage.inputTokens.toLocaleString()}, Received: ${tokenUsage.outputTokens.toLocaleString()}, Total: ${total.toLocaleString()}`}
78+
title={trans.__(
79+
'Token Usage - Sent: %1, Received: %2, Total: %3',
80+
tokenUsage.inputTokens.toLocaleString(),
81+
tokenUsage.outputTokens.toLocaleString(),
82+
total.toLocaleString()
83+
)}
7284
>
7385
<span
7486
style={{

0 commit comments

Comments
 (0)