diff --git a/locale/fr.json b/locale/fr.json new file mode 100644 index 0000000..5eedade --- /dev/null +++ b/locale/fr.json @@ -0,0 +1,76 @@ +{ + "Chat and completer provider": "Fournisseur de chat et de compléteur", + "Chat provider": "Fournisseur de chat", + "Completer provider": "Fournisseur de compléteur", + "The provider registry is needed to enable the jupyterlite-ai settings panel": "Le registre des fournisseurs est nécessaire pour activer le panneau des paramètres de jupyterlite-ai", + "The provider is missing from the completer settings": "Le fournisseur est manquant dans les paramètres du compléteur", + "The AI provider to use for chat and completion": "Le fournisseur d'IA à utiliser pour le chat et la complétion", + "The provider is missing from the chat settings": "Le fournisseur est manquant dans les paramètres du chat", + "Instructions": "Instructions", + "Restore to Defaults": "Restaurer les valeurs par défaut", + "Stop streaming": "Arrêter le streaming", + "#### Ask JupyterLite AI": "#### Demander à JupyterLite AI", + "The provider to use can be set in the": "Le fournisseur à utiliser peut être défini dans l'", + "settings editor": "éditeur de paramètres", + "by selecting it from the": "en le sélectionnant depuis les", + "_AI provider_ settings": "paramètres _Fournisseur d'IA_", + "The current providers that are available are": "Les fournisseurs actuellement disponibles sont", + "To clear the chat, you can use the": "Pour effacer le chat, vous pouvez utiliser la", + "command from the chat input": "commande depuis l'entrée du chat", + "AI provider not configured": "Fournisseur d'IA non configuré", + "/clear": "/effacer", + "Clear the chat": "Effacer le chat", + "A AI provider named '%1' is already registered": "Un fournisseur d'IA nommé '%1' est déjà enregistré", + "Autocompletion registry": "Registre d'autocomplétion", + "LLM chat extension": "Extension de chat LLM", + "The SettingsRegistry is not loaded for the chat extension": "Le SettingsRegistry n'est pas chargé pour l'extension de chat", + "Something went wrong when reading the settings.\n%1": "Quelque chose s'est mal passé lors de la lecture des paramètres.\n%1", + "Jupyterlite AI Chat": "Chat JupyterLite AI", + "Failed to load settings for %1": "Échec du chargement des paramètres pour %1", + "AI provider": "Fournisseur d'IA", + "Provider settings": "Paramètres du fournisseur", + "Support for ChromeAI is still experimental and only available in Google Chrome.": "Le support de ChromeAI est encore expérimental et uniquement disponible dans Google Chrome.", + "You can test ChromeAI is enabled in your browser by going to the following URL:": "Vous pouvez tester si ChromeAI est activé dans votre navigateur en accédant à l'URL suivante :", + "Enable the proper flags in Google Chrome.": "Activez les drapeaux appropriés dans Google Chrome.", + "Then restart Chrome for these changes to take effect.": "Redémarrez ensuite Chrome pour que ces modifications prennent effet.", + "On first use, Chrome will download the on-device model, which can be as large as 22GB (according to their docs and at the time of writing).": "À la première utilisation, Chrome téléchargera le modèle sur appareil, qui peut faire jusqu'à 22 Go (selon leur documentation et au moment de l'écriture).", + "During the download, ChromeAI may not be available via the extension.": "Pendant le téléchargement, ChromeAI peut ne pas être disponible via l'extension.", + "For more information about Chrome Built-in AI:": "Pour plus d'informations sur l'IA intégrée de Chrome :", + "Your browser does not support ChromeAI. Please use an updated chrome based browser like Google Chrome, and follow the instructions in settings to enable it.": "Votre navigateur ne supporte pas ChromeAI. Veuillez utiliser un navigateur basé sur Chrome à jour comme Google Chrome, et suivez les instructions dans les paramètres pour l'activer.", + "The ChromeAI model is not available in your browser. Please ensure you have enabled the necessary flags in Google Chrome as described in the instructions in settings.": "Le modèle ChromeAI n'est pas disponible dans votre navigateur. Veuillez vous assurer d'avoir activé les drapeaux nécessaires dans Google Chrome comme décrit dans les instructions des paramètres.", + "This extension is still very much experimental. It is not an official Google extension.": "Cette extension est encore très expérimentale. Ce n'est pas une extension officielle de Google.", + "Go to and create an API key.": "Allez sur et créez une clé API.", + "Open the JupyterLab settings and go to the **Ai providers** section to select the `Gemini` provider and add your API key (required).": "Ouvrez les paramètres de JupyterLab et allez dans la section **Fournisseurs d'IA** pour sélectionner le fournisseur `Gemini` et ajouter votre clé API (obligatoire).", + "Open the chat, or use the inline completer.": "Ouvrez le chat, ou utilisez le compléteur en ligne.", + "This extension is still very much experimental. It is not an official MistralAI extension.": "Cette extension est encore très expérimentale. Ce n'est pas une extension officielle de MistralAI.", + "Go to and create an API key.": "Allez sur et créez une clé API.", + "Open the JupyterLab settings and go to the **Ai providers** section to select the `MistralAI` provider and the API key (required).": "Ouvrez les paramètres de JupyterLab et allez dans la section **Fournisseurs d'IA** pour sélectionner le fournisseur `MistralAI` et la clé API (obligatoire).", + "**Note:** When using MistralAI for completions, only a subset of models are available. Please check [this resource](https://docs.mistral.ai/api/#tag/fim) to see the list of supported models for completions.": "**Note :** Lors de l'utilisation de MistralAI pour les complétions, seul un sous-ensemble de modèles est disponible. Veuillez vérifier [cette ressource](https://docs.mistral.ai/api/#tag/fim) pour voir la liste des modèles supportés pour les complétions.", + "Open the chat, or use the inline completer": "Ouvrez le chat, ou utilisez le compléteur en ligne", + "Ollama allows to run large language models locally on your machine.": "Ollama permet d'exécuter de grands modèles de langage localement sur votre machine.", + "To use it you need to install the Ollama CLI and pull the model you want to use.": "Pour l'utiliser, vous devez installer la CLI Ollama et télécharger le modèle que vous souhaitez utiliser.", + "Install the Ollama CLI by following the instructions at ": "Installez la CLI Ollama en suivant les instructions sur ", + "Pull the model you want to use by running the following command in your terminal:": "Téléchargez le modèle que vous souhaitez utiliser en exécutant la commande suivante dans votre terminal :", + "For example, to pull the Llama 2 model, run:": "Par exemple, pour télécharger le modèle Llama 2, exécutez :", + "Once the model is pulled, you can use it in your application by running the following command:": "Une fois le modèle téléchargé, vous pouvez l'utiliser dans votre application en exécutant la commande suivante :", + "This model will be available in the extension, using the model name you used in the command above.": "Ce modèle sera disponible dans l'extension, en utilisant le nom de modèle que vous avez utilisé dans la commande ci-dessus.", + "Deploying Lite/Lab on external server": "Déploiement de Lite/Lab sur un serveur externe", + "See https://objectgraph.com/blog/ollama-cors/ for more details.": "Voir https://objectgraph.com/blog/ollama-cors/ pour plus de détails.", + "On Linux, you can run the following commands:": "Sur Linux, vous pouvez exécuter les commandes suivantes :", + "Check if CORS is enabled on the server. You can do this by running the following command in your terminal:": "Vérifiez si CORS est activé sur le serveur. Vous pouvez le faire en exécutant la commande suivante dans votre terminal :", + "If CORS is disabled, you will see a response like this:": "Si CORS est désactivé, vous verrez une réponse comme celle-ci :", + "If CORS is not enabled, update _/etc/systemd/system/ollama.service_ with:": "Si CORS n'est pas activé, mettez à jour _/etc/systemd/system/ollama.service_ avec :", + "Restart the service:": "Redémarrez le service :", + "Check if CORS is enabled on the server again by running the following command in your terminal:": "Vérifiez à nouveau si CORS est activé sur le serveur en exécutant la commande suivante dans votre terminal :", + "WebLLM enables running LLMs directly in your browser, making it possible to use AI features without sending data to external servers.": "WebLLM permet d'exécuter des LLM directement dans votre navigateur, permettant d'utiliser les fonctionnalités d'IA sans envoyer de données vers des serveurs externes.", + "WebLLM runs models entirely in your browser, so initial model download may be large (100MB-2GB depending on the model).": "WebLLM exécute les modèles entièrement dans votre navigateur, donc le téléchargement initial du modèle peut être important (100MB-2GB selon le modèle).", + "**Requirements:** WebLLM requires a browser with WebGPU support (Chrome 113+, Edge 113+, or Safari 17+). It will not work on older browsers or browsers without WebGPU enabled.": "**Exigences :** WebLLM nécessite un navigateur avec support WebGPU (Chrome 113+, Edge 113+, ou Safari 17+). Il ne fonctionnera pas sur les navigateurs plus anciens ou les navigateurs sans WebGPU activé.", + "Enter a model in the JupyterLab settings under the **Ai providers** section. Select the `WebLLM` provider and type the model you want to use.": "Entrez un modèle dans les paramètres de JupyterLab sous la section **Fournisseurs d'IA**. Sélectionnez le fournisseur `WebLLM` et tapez le modèle que vous souhaitez utiliser.", + "When you first use WebLLM, your browser will download the model. A progress notification will appear:": "Quand vous utilisez WebLLM pour la première fois, votre navigateur téléchargera le modèle. Une notification de progression apparaîtra :", + "Once loaded, use the chat": "Une fois chargé, utilisez le chat", + "Example of available models:": "Exemple de modèles disponibles :", + "See the full list of models: https://github.com/mlc-ai/web-llm/blob/632d34725629b480b5b2772379ef5c150b1286f0/src/config.ts#L303-L309": "Voir la liste complète des modèles : https://github.com/mlc-ai/web-llm/blob/632d34725629b480b5b2772379ef5c150b1286f0/src/config.ts#L303-L309", + "Model performance depends on your device's hardware capabilities. More powerful devices will run models faster. Some larger models may not work well on devices with limited GPU memory or may experience slow response times.": "Les performances du modèle dépendent des capacités matérielles de votre appareil. Les appareils plus puissants exécuteront les modèles plus rapidement. Certains modèles plus grands peuvent ne pas bien fonctionner sur des appareils avec une mémoire GPU limitée ou peuvent subir des temps de réponse lents.", + "Your browser does not support WebLLM, it does not support required WebGPU.": "Votre navigateur ne supporte pas WebLLM, il ne supporte pas WebGPU requis.", + "You may need to enable WebGPU, `await navigator.gpu.requestAdapter()` is null.": "Vous devrez peut-être activer WebGPU, `await navigator.gpu.requestAdapter()` est null." +} diff --git a/package.json b/package.json index 4180e95..5f9a565 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,md}", "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}", "src/**/*.{ts,tsx}", - "schema/*.json" + "schema/*.json", + "locale/*.json" ], "main": "lib/index.js", "types": "lib/index.d.ts", @@ -27,13 +28,14 @@ "url": "https://github.com/jupyterlite/ai.git" }, "scripts": { - "build": "jlpm build:lib && jlpm build:labextension:dev", - "build:dev": "jlpm build:lib && jlpm build:labextension:dev", - "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", + "build": "jlpm build:lib && jlpm build:labextension:dev && jlpm copy:locale", + "build:dev": "jlpm build:lib && jlpm build:labextension:dev && jlpm copy:locale", + "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension && jlpm copy:locale", "build:labextension": "jupyter labextension build .", "build:labextension:dev": "jupyter labextension build --development True .", "build:lib": "tsc --sourceMap", "build:lib:prod": "tsc", + "copy:locale": "node scripts/copy-locale.js", "clean": "jlpm clean:lib", "clean:lib": "rimraf lib tsconfig.tsbuildinfo", "clean:lintcache": "rimraf .eslintcache .stylelintcache", @@ -64,6 +66,7 @@ "@jupyterlab/notebook": "^4.4.0", "@jupyterlab/rendermime": "^4.4.0", "@jupyterlab/settingregistry": "^4.4.0", + "@jupyterlab/translation": "^4.4.3", "@jupyterlab/ui-components": "^4.4.0", "@langchain/anthropic": "^0.3.24", "@langchain/community": "^0.3.48", diff --git a/pyproject.toml b/pyproject.toml index eea044c..ad71e25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,9 @@ npm = ["jlpm"] source_dir = "src" build_dir = "jupyterlite_ai/labextension" +[tool.hatch.build.hooks.custom] +path = "scripts/copy-locale.js" + [tool.jupyter-releaser.options] version_cmd = "hatch version" diff --git a/scripts/copy-locale.js b/scripts/copy-locale.js new file mode 100644 index 0000000..f38d0fb --- /dev/null +++ b/scripts/copy-locale.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// Paths +const sourcePath = path.join(__dirname, '..', 'locale'); +const targetPath = path.join(__dirname, '..', 'jupyterlite_ai', 'labextension', 'locale'); + +// Function to copy directory recursively +function copyDir(src, dest) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true }); + } + + const files = fs.readdirSync(src); + + files.forEach(file => { + const srcFile = path.join(src, file); + const destFile = path.join(dest, file); + + if (fs.statSync(srcFile).isDirectory()) { + copyDir(srcFile, destFile); + } else { + fs.copyFileSync(srcFile, destFile); + } + }); +} + +// Copy locale files +if (fs.existsSync(sourcePath)) { + console.log('Copying locale files...'); + copyDir(sourcePath, targetPath); + console.log('✅ Locale files copied successfully'); +} else { + console.log('❌ Source locale directory not found'); +} \ No newline at end of file diff --git a/src/chat-handler.ts b/src/chat-handler.ts index 4d10fe5..8bf89f5 100644 --- a/src/chat-handler.ts +++ b/src/chat-handler.ts @@ -22,6 +22,7 @@ import { SystemMessage } from '@langchain/core/messages'; import { UUID } from '@lumino/coreutils'; +import { TranslationBundle } from '@jupyterlab/translation'; import { DEFAULT_CHAT_SYSTEM_PROMPT } from './default-prompts'; import { jupyternautLiteIcon } from './icons'; @@ -35,16 +36,19 @@ import { AIChatModel } from './types/ai-model'; const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr); const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`; -export const welcomeMessage = (providers: string[]) => ` -#### Ask JupyterLite AI +export const welcomeMessage = ( + providers: string[], + trans: TranslationBundle +) => ` +${trans.__('#### Ask JupyterLite AI')} -The provider to use can be set in the , by selecting it from -the _AI provider_ settings. +${trans.__('The provider to use can be set in the')} , ${trans.__('by selecting it from the')} + _${trans.__('AI provider')}_ ${trans.__('settings')}. -The current providers that are available are _${providers.sort().join('_, _')}_. +${trans.__('The current providers that are available are')} _${providers.sort().join('_, _')}_. -To clear the chat, you can use the \`/clear\` command from the chat input. +${trans.__('To clear the chat, you can use the')} \`${trans.__('/clear')}\` ${trans.__('command from the chat input')}. `; export type ConnectionMessage = { @@ -56,6 +60,7 @@ export class ChatHandler extends AbstractChatModel { constructor(options: ChatHandler.IOptions) { super(options); this._providerRegistry = options.providerRegistry; + this._translator = options.translator; this._providerRegistry.providerChanged.connect(() => { this._errorMessage = this._providerRegistry.chatError; @@ -94,7 +99,7 @@ export class ChatHandler extends AbstractChatModel { async sendMessage(message: INewMessage): Promise { const body = message.body; - if (body.startsWith('/clear')) { + if (body.startsWith(this._translator.__('/clear'))) { // TODO: do we need a clear method? this.messagesDeleted(0, this.messages.length); this._history.messages = []; @@ -201,8 +206,12 @@ export class ChatHandler extends AbstractChatModel { private _personaName = 'AI'; private _errorMessage: string = ''; private _history: IChatHistory = { messages: [] }; - private _defaultErrorMessage = 'AI provider not configured'; + private _translator: TranslationBundle; private _controller: AbortController | null = null; + + get _defaultErrorMessage(): string { + return this._translator.__('AI provider not configured'); + } } export namespace ChatHandler { @@ -211,6 +220,7 @@ export namespace ChatHandler { */ export interface IOptions extends IChatModel.IOptions { providerRegistry: IAIProviderRegistry; + translator: TranslationBundle; } /** @@ -225,14 +235,22 @@ export namespace ChatHandler { */ export class ClearCommandProvider implements IChatCommandProvider { public id: string = '@jupyterlite/ai:clear-commands'; - private _slash_commands: ChatCommand[] = [ - { - name: '/clear', - providerId: this.id, - replaceWith: '/clear', - description: 'Clear the chat' - } - ]; + private _translator: TranslationBundle; + + constructor(translator: TranslationBundle) { + this._translator = translator; + } + + private get _slash_commands(): ChatCommand[] { + return [ + { + name: this._translator.__('/clear'), + providerId: this.id, + replaceWith: this._translator.__('/clear'), + description: this._translator.__('Clear the chat') + } + ]; + } async listCommandCompletions(inputModel: IInputModel) { const match = inputModel.currentWord?.match(/^\/\w*/)?.[0]; if (!match) { diff --git a/src/components/stop-button.tsx b/src/components/stop-button.tsx index 245bc32..c5a3375 100644 --- a/src/components/stop-button.tsx +++ b/src/components/stop-button.tsx @@ -7,6 +7,7 @@ import StopIcon from '@mui/icons-material/Stop'; import React from 'react'; import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat'; +import { TranslationBundle } from '@jupyterlab/translation'; /** * Properties of the stop button. @@ -17,13 +18,17 @@ export interface IStopButtonProps * The function to stop streaming. */ stopStreaming: () => void; + /** + * The translation bundle. + */ + trans: TranslationBundle; } /** * The stop button. */ export function StopButton(props: IStopButtonProps): JSX.Element { - const tooltip = 'Stop streaming'; + const tooltip = props.trans.__('Stop streaming'); return ( void + stopStreaming: () => void, + trans: TranslationBundle ): InputToolbarRegistry.IToolbarItem { return { element: (props: InputToolbarRegistry.IToolbarItemProps) => { - const stopProps: IStopButtonProps = { ...props, stopStreaming }; + const stopProps: IStopButtonProps = { ...props, stopStreaming, trans }; return StopButton(stopProps); }, position: 50, diff --git a/src/index.ts b/src/index.ts index 06e870d..0903adc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import { ICompletionProviderManager } from '@jupyterlab/completer'; import { INotebookTracker } from '@jupyterlab/notebook'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; +import { ITranslator } from '@jupyterlab/translation'; import { IFormRendererRegistry } from '@jupyterlab/ui-components'; import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager'; @@ -34,9 +35,11 @@ const chatCommandRegistryPlugin: JupyterFrontEndPlugin = { description: 'Autocompletion registry', autoStart: true, provides: IChatCommandRegistry, - activate: () => { + requires: [ITranslator], + activate: (app: JupyterFrontEnd, translator: ITranslator) => { + const trans = translator.load('jupyterlite_ai'); const registry = new ChatCommandRegistry(); - registry.addProvider(new ChatHandler.ClearCommandProvider()); + registry.addProvider(new ChatHandler.ClearCommandProvider(trans)); return registry; } }; @@ -45,7 +48,12 @@ const chatPlugin: JupyterFrontEndPlugin = { id: PLUGIN_IDS.chat, description: 'LLM chat extension', autoStart: true, - requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry], + requires: [ + IAIProviderRegistry, + IRenderMimeRegistry, + IChatCommandRegistry, + ITranslator + ], optional: [ INotebookTracker, ISettingRegistry, @@ -57,11 +65,13 @@ const chatPlugin: JupyterFrontEndPlugin = { providerRegistry: IAIProviderRegistry, rmRegistry: IRenderMimeRegistry, chatCommandRegistry: IChatCommandRegistry, + translator: ITranslator, notebookTracker: INotebookTracker | null, settingsRegistry: ISettingRegistry | null, themeManager: IThemeManager | null, restorer: ILayoutRestorer | null ) => { + const trans = translator.load('jupyterlite_ai'); let activeCellManager: IActiveCellManager | null = null; if (notebookTracker) { activeCellManager = new ActiveCellManager({ @@ -72,7 +82,8 @@ const chatPlugin: JupyterFrontEndPlugin = { const chatHandler = new ChatHandler({ providerRegistry, - activeCellManager + activeCellManager, + translator: trans }); let sendWithShiftEnter = false; @@ -94,7 +105,9 @@ const chatPlugin: JupyterFrontEndPlugin = { .then(([, settings]) => { if (!settings) { console.warn( - 'The SettingsRegistry is not loaded for the chat extension' + trans.__( + 'The SettingsRegistry is not loaded for the chat extension' + ) ); return; } @@ -103,14 +116,17 @@ const chatPlugin: JupyterFrontEndPlugin = { }) .catch(reason => { console.error( - `Something went wrong when reading the settings.\n${reason}` + trans.__( + 'Something went wrong when reading the settings.\n%1', + reason + ) ); }); let chatWidget: ReactWidget | null = null; const inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry(); - const stopButton = stopItem(() => chatHandler.stopStreaming()); + const stopButton = stopItem(() => chatHandler.stopStreaming(), trans); inputToolbarRegistry.addItem('stop', stopButton); chatHandler.writersChanged.connect((_, writers) => { @@ -134,13 +150,13 @@ const chatPlugin: JupyterFrontEndPlugin = { rmRegistry, chatCommandRegistry, inputToolbarRegistry, - welcomeMessage: welcomeMessage(providerRegistry.providers) + welcomeMessage: welcomeMessage(providerRegistry.providers, trans) }); } catch (e) { chatWidget = buildErrorWidget(themeManager); } - chatWidget.title.caption = 'Jupyterlite AI Chat'; + chatWidget.title.caption = trans.__('Jupyterlite AI Chat'); chatWidget.id = '@jupyterlite/ai:chat-widget'; app.shell.add(chatWidget as ReactWidget, 'left', { rank: 2000 }); @@ -172,16 +188,18 @@ const providerRegistryPlugin: JupyterFrontEndPlugin = SecretsManager.sign(PLUGIN_IDS.providerRegistry, token => ({ id: PLUGIN_IDS.providerRegistry, autoStart: true, - requires: [IFormRendererRegistry, ISettingRegistry], + requires: [IFormRendererRegistry, ISettingRegistry, ITranslator], optional: [IRenderMimeRegistry, ISecretsManager], provides: IAIProviderRegistry, activate: ( app: JupyterFrontEnd, editorRegistry: IFormRendererRegistry, settingRegistry: ISettingRegistry, + translator: ITranslator, rmRegistry?: IRenderMimeRegistry, secretsManager?: ISecretsManager ): IAIProviderRegistry => { + const trans = translator.load('jupyterlite_ai'); const providerRegistry = new AIProviderRegistry({ token, secretsManager @@ -193,7 +211,8 @@ const providerRegistryPlugin: JupyterFrontEndPlugin = providerRegistry, secretsToken: token, rmRegistry, - secretsManager + secretsManager, + translator }) ); @@ -232,7 +251,10 @@ const providerRegistryPlugin: JupyterFrontEndPlugin = }) .catch(reason => { console.error( - `Failed to load settings for ${providerRegistryPlugin.id}`, + trans.__( + 'Failed to load settings for %1', + providerRegistryPlugin.id + ), reason ); }); @@ -244,14 +266,16 @@ const providerRegistryPlugin: JupyterFrontEndPlugin = const systemPromptsPlugin: JupyterFrontEndPlugin = { id: PLUGIN_IDS.systemPrompts, autoStart: true, - requires: [IAIProviderRegistry, ISettingRegistry], + requires: [IAIProviderRegistry, ISettingRegistry, ITranslator], optional: [IFormRendererRegistry], activate: ( app: JupyterFrontEnd, providerRegistry: IAIProviderRegistry, settingsRegistry: ISettingRegistry, + translator: ITranslator, editorRegistry: IFormRendererRegistry | null ): void => { + const trans = translator.load('jupyterlite_ai'); // Set textarea renderer for the prompt setting. editorRegistry?.addRenderer( `${PLUGIN_IDS.systemPrompts}.chatSystemPrompt`, @@ -280,7 +304,9 @@ const systemPromptsPlugin: JupyterFrontEndPlugin = { .then(([, settings]) => { if (!settings) { console.warn( - 'The SettingsRegistry is not loaded for the chat extension' + trans.__( + 'The SettingsRegistry is not loaded for the chat extension' + ) ); return; } @@ -289,7 +315,10 @@ const systemPromptsPlugin: JupyterFrontEndPlugin = { }) .catch(reason => { console.error( - `Something went wrong when reading the settings.\n${reason}` + trans.__( + 'Something went wrong when reading the settings.\n%1', + reason + ) ); }); } diff --git a/src/settings/panel.tsx b/src/settings/panel.tsx index 2fd9976..296582e 100644 --- a/src/settings/panel.tsx +++ b/src/settings/panel.tsx @@ -1,5 +1,6 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; +import { ITranslator, TranslationBundle } from '@jupyterlab/translation'; import { Button, FormComponent, @@ -31,6 +32,7 @@ export const aiSettingsRenderer = (options: { secretsToken?: symbol; rmRegistry?: IRenderMimeRegistry; secretsManager?: ISecretsManager; + translator?: ITranslator; }): IFormRenderer => { const { secretsToken } = options; delete options.secretsToken; @@ -77,6 +79,10 @@ export class AiSettings constructor(props: FieldProps) { super(props); this._settings = props.formContext.settings; + this._translator = props.formContext.translator; + this._trans = this._translator + ? this._translator.load('jupyterlite_ai') + : null; const uniqueProvider = (this._settings.get('UniqueProvider').composite as boolean) ?? true; @@ -175,13 +181,21 @@ export class AiSettings

{this.state.uniqueProvider - ? 'Chat and completer provider' - : 'Chat provider'} + ? this._trans + ? this._trans.__('Chat and completer provider') + : 'Chat and completer provider' + : this._trans + ? this._trans.__('Chat provider') + : 'Chat provider'}

{!this.state.uniqueProvider && ( <> -

Completer provider

+

+ {this._trans + ? this._trans.__('Completer provider') + : 'Completer provider'} +

- Instructions + + {this._trans ? this._trans.__('Instructions') : 'Instructions'} + node && node.replaceChildren(this.state.instruction!) @@ -674,7 +704,9 @@ export class AiProviderSettings extends React.Component<
{this.state.isModified && ( )}
@@ -707,6 +739,8 @@ export class AiProviderSettings extends React.Component< private _formRef = React.createRef(); private _secretFields: string[] = []; private _defaultFormData: IDict = {}; + private _translator: ITranslator | undefined; + private _trans: TranslationBundle | null; } /** diff --git a/tsconfig.json b/tsconfig.json index 1e27c33..b8b5974 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,6 +28,7 @@ "src/*", "src/**/*", "src/settings/base.json", - "src/default-providers/**/*.json" + "src/default-providers/**/*.json", + "locale/*.json" ] }