diff --git a/frontend/src/lib/components/sidebar/OperatorMenu.svelte b/frontend/src/lib/components/sidebar/OperatorMenu.svelte
index 2b916550ff..5f55f1ad0f 100644
--- a/frontend/src/lib/components/sidebar/OperatorMenu.svelte
+++ b/frontend/src/lib/components/sidebar/OperatorMenu.svelte
@@ -38,6 +38,7 @@
import MenuButton, { sidebarClasses } from './MenuButton.svelte'
import MenuLink from './MenuLink.svelte'
import type { FavoriteKind } from './FavoriteMenu.svelte'
+ import { t } from '$lib/i18n/t.svelte'
let darkMode: boolean = $state(false)
let showExtraTriggers = $state(false)
let menubarEl: HTMLElement | undefined = $state()
@@ -55,14 +56,14 @@
let mainMenuLinks = $derived(
[
- { label: 'Home', id: 'home', href: `${base}/`, icon: Home },
- { label: 'Runs', id: 'runs', href: `${base}/runs`, icon: Play },
- { label: 'Schedules', id: 'schedules', href: `${base}/schedules`, icon: Calendar },
+ { label: t('sidebar.home'), id: 'home', href: `${base}/`, icon: Home },
+ { label: t('sidebar.runs'), id: 'runs', href: `${base}/runs`, icon: Play },
+ { label: t('sidebar.schedules'), id: 'schedules', href: `${base}/schedules`, icon: Calendar },
// Add Tutorials to main menu only if not all completed and not skipped
...($tutorialsToDo.length > 0 && !$skippedAll
? [
{
- label: 'Tutorials',
+ label: t('sidebar.tutorials'),
id: 'tutorials',
href: `${base}/tutorials`,
icon: GraduationCap
@@ -89,37 +90,37 @@
let secondMenuLinks: SecondMenuLink[] = $derived(
[
{
- label: 'Resources',
+ label: t('sidebar.resources'),
id: 'resources',
href: `${base}/resources`
},
{
- label: 'Variables',
+ label: t('sidebar.variables'),
id: 'variables',
href: `${base}/variables`
},
{
- label: 'Assets',
+ label: t('sidebar.assets'),
id: 'assets',
href: `${base}/assets`
},
{
- label: 'Groups',
+ label: t('sidebar.groups'),
id: 'groups',
href: `${base}/groups`
},
{
- label: 'Folders',
+ label: t('sidebar.folders'),
id: 'folders',
href: `${base}/folders`
},
{
- label: 'Workers',
+ label: t('sidebar.workers'),
id: 'workers',
href: `${base}/workers`
},
{
- label: 'Audit logs',
+ label: t('sidebar.audit_logs'),
id: 'audit_logs',
href: `${base}/audit_logs`
}
@@ -129,30 +130,60 @@
let allTriggerLinks: TriggerMenuLink[] = $derived(
(
[
- { label: 'Custom HTTP routes', id: 'triggers', href: `${base}/routes`, kind: 'http' },
{
- label: 'Websocket triggers',
+ label: t('operator.custom_http_routes'),
+ id: 'triggers',
+ href: `${base}/routes`,
+ kind: 'http'
+ },
+ {
+ label: t('operator.websocket_triggers'),
id: 'triggers',
href: `${base}/websocket_triggers`,
kind: 'ws'
},
{
- label: 'Postgres triggers',
+ label: t('operator.postgres_triggers'),
id: 'triggers',
href: `${base}/postgres_triggers`,
kind: 'postgres'
},
- { label: 'Kafka triggers', id: 'triggers', href: `${base}/kafka_triggers`, kind: 'kafka' },
- { label: 'NATS triggers', id: 'triggers', href: `${base}/nats_triggers`, kind: 'nats' },
- { label: 'SQS triggers', id: 'triggers', href: `${base}/sqs_triggers`, kind: 'sqs' },
{
- label: 'GCP Pub/Sub triggers',
+ label: t('operator.kafka_triggers'),
+ id: 'triggers',
+ href: `${base}/kafka_triggers`,
+ kind: 'kafka'
+ },
+ {
+ label: t('operator.nats_triggers'),
+ id: 'triggers',
+ href: `${base}/nats_triggers`,
+ kind: 'nats'
+ },
+ {
+ label: t('operator.sqs_triggers'),
+ id: 'triggers',
+ href: `${base}/sqs_triggers`,
+ kind: 'sqs'
+ },
+ {
+ label: t('operator.gcp_triggers'),
id: 'triggers',
href: `${base}/gcp_triggers`,
kind: 'gcp'
},
- { label: 'MQTT triggers', id: 'triggers', href: `${base}/mqtt_triggers`, kind: 'mqtt' },
- { label: 'Email triggers', id: 'triggers', href: `${base}/email_triggers`, kind: 'email' }
+ {
+ label: t('operator.mqtt_triggers'),
+ id: 'triggers',
+ href: `${base}/mqtt_triggers`,
+ kind: 'mqtt'
+ },
+ {
+ label: t('operator.email_triggers'),
+ id: 'triggers',
+ href: `${base}/email_triggers`,
+ kind: 'email'
+ }
] as TriggerMenuLink[]
).filter(filterLink)
)
@@ -231,7 +262,7 @@
{item}
>
- Account settings
+ {t('user.account_settings')}
@@ -259,7 +290,7 @@
{:else}
{/if}
- Switch theme
+ {t('user.switch_theme')}
{/if}
@@ -303,7 +334,7 @@
{item}
>
- Sign out
+ {t('user.sign_out')}
diff --git a/frontend/src/lib/components/sidebar/SidebarContent.svelte b/frontend/src/lib/components/sidebar/SidebarContent.svelte
index b0382158bf..ff85b719c3 100644
--- a/frontend/src/lib/components/sidebar/SidebarContent.svelte
+++ b/frontend/src/lib/components/sidebar/SidebarContent.svelte
@@ -73,6 +73,7 @@
} from '$lib/components/meltComponents'
import MenuButton from './MenuButton.svelte'
import GoogleCloudIcon from '../icons/GoogleCloudIcon.svelte'
+ import { t } from '$lib/i18n/t.svelte'
async function leaveWorkspace() {
await WorkspaceService.leaveWorkspace({ workspace: $workspaceStore ?? '' })
@@ -133,13 +134,13 @@
hasNewChangelogs = false
}
- const thirdMenuLinks = [
+ let thirdMenuLinks = $derived([
{
- label: 'Help',
+ label: t('sidebar.help'),
icon: HelpCircle,
subItems: [
{
- label: 'Tutorials',
+ label: t('sidebar.tutorials'),
href: `${base}/tutorials`,
icon: GraduationCap,
aiId: 'sidebar-menu-link-tutorials',
@@ -147,7 +148,7 @@
external: false
},
{
- label: 'Docs',
+ label: t('sidebar.docs'),
href: 'https://www.windmill.dev/docs/intro/',
icon: BookOpen,
aiId: 'sidebar-menu-link-docs',
@@ -155,7 +156,7 @@
external: true
},
{
- label: 'Feedbacks',
+ label: t('sidebar.feedbacks'),
href: 'https://discord.gg/V7PM2YHsPB',
icon: DiscordIcon,
aiId: 'sidebar-menu-link-feedbacks',
@@ -163,7 +164,7 @@
external: true
},
{
- label: 'Issues',
+ label: t('sidebar.issues'),
href: 'https://github.com/windmill-labs/windmill/issues/new',
icon: Github,
aiId: 'sidebar-menu-link-issues',
@@ -171,7 +172,7 @@
external: true
},
{
- label: 'Changelog',
+ label: t('sidebar.changelog'),
href: 'https://www.windmill.dev/changelog/',
icon: Newspaper,
aiId: 'sidebar-menu-link-changelog',
@@ -180,7 +181,7 @@
}
]
}
- ]
+ ])
interface Props {
numUnacknowledgedCriticalAlerts?: number
@@ -205,7 +206,7 @@
)
let mainMenuLinks = $derived([
{
- label: 'Home',
+ label: t('sidebar.home'),
href: `${base}/`,
icon: Home,
aiId: 'sidebar-menu-link-home',
@@ -213,7 +214,7 @@
"Button to navigate to home which contains all the user's scripts, flows and apps"
},
{
- label: 'Runs',
+ label: t('sidebar.runs'),
href: `${base}/runs`,
icon: Play,
aiId: 'sidebar-menu-link-runs',
@@ -225,7 +226,7 @@
}
},
{
- label: 'Variables',
+ label: t('sidebar.variables'),
href: `${base}/variables`,
icon: DollarSign,
disabled: $userStore?.operator,
@@ -233,7 +234,7 @@
aiDescription: 'Button to navigate to variables'
},
{
- label: 'Resources',
+ label: t('sidebar.resources'),
href: `${base}/resources`,
icon: Boxes,
disabled: $userStore?.operator,
@@ -241,7 +242,7 @@
aiDescription: 'Button to navigate to resources'
},
{
- label: 'Assets',
+ label: t('sidebar.assets'),
href: `${base}/assets`,
icon: Pyramid,
disabled: $userStore?.operator,
@@ -252,7 +253,7 @@
...($tutorialsToDo.length > 0 && !$skippedAll
? [
{
- label: 'Tutorials',
+ label: t('sidebar.tutorials'),
href: `${base}/tutorials`,
icon: GraduationCap,
aiId: 'sidebar-menu-link-tutorials-main',
@@ -263,7 +264,7 @@
])
let defaultExtraTriggerLinks = $derived([
{
- label: 'HTTP',
+ label: t('sidebar.http'),
href: '/routes',
icon: Route,
disabled: $userStore?.operator,
@@ -272,7 +273,7 @@
aiDescription: 'Button to navigate to HTTP routes'
},
{
- label: 'WebSockets',
+ label: t('sidebar.websockets'),
href: '/websocket_triggers',
icon: Unplug,
disabled: $userStore?.operator,
@@ -281,7 +282,7 @@
aiDescription: 'Button to navigate to websocket triggers'
},
{
- label: 'Postgres',
+ label: t('sidebar.postgres'),
href: '/postgres_triggers',
icon: Database,
disabled: $userStore?.operator,
@@ -290,7 +291,7 @@
aiDescription: 'Button to navigate to Postgres triggers'
},
{
- label: 'Kafka' + ($enterpriseLicense ? '' : ' (EE)'),
+ label: t('sidebar.kafka') + ($enterpriseLicense ? '' : ' (EE)'),
href: '/kafka_triggers',
icon: KafkaIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
@@ -299,7 +300,7 @@
aiDescription: 'Button to navigate to Kafka triggers'
},
{
- label: 'NATS' + ($enterpriseLicense ? '' : ' (EE)'),
+ label: t('sidebar.nats') + ($enterpriseLicense ? '' : ' (EE)'),
href: '/nats_triggers',
icon: NatsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
@@ -308,7 +309,7 @@
aiDescription: 'Button to navigate to NATS triggers'
},
{
- label: 'SQS' + ($enterpriseLicense ? '' : ' (EE)'),
+ label: t('sidebar.sqs') + ($enterpriseLicense ? '' : ' (EE)'),
href: '/sqs_triggers',
icon: AwsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
@@ -317,7 +318,7 @@
aiDescription: 'Button to navigate to SQS triggers'
},
{
- label: 'GCP Pub/Sub' + ($enterpriseLicense ? '' : ' (EE)'),
+ label: t('sidebar.gcp_pubsub') + ($enterpriseLicense ? '' : ' (EE)'),
href: '/gcp_triggers',
icon: GoogleCloudIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
@@ -326,7 +327,7 @@
aiDescription: 'Button to navigate to GCP Pub/Sub triggers'
},
{
- label: 'MQTT',
+ label: t('sidebar.mqtt'),
href: '/mqtt_triggers',
icon: MqttIcon,
disabled: $userStore?.operator,
@@ -335,7 +336,7 @@
aiDescription: 'Button to navigate to MQTT triggers'
},
{
- label: 'Email',
+ label: t('sidebar.email'),
href: '/email_triggers',
icon: MailIcon,
disabled: $userStore?.operator,
@@ -361,7 +362,7 @@
let triggerMenuLinks = $derived([
{
- label: 'Schedules',
+ label: t('sidebar.schedules'),
href: `${base}/schedules`,
icon: Calendar,
disabled: !SIDEBAR_SHOW_SCHEDULES || $userStore?.operator,
@@ -385,14 +386,14 @@
// disabled: !$userStore?.is_admin && !$userStore?.is_super_admin
// },
{
- label: 'Settings',
+ label: t('sidebar.settings'),
icon: Settings,
aiId: 'sidebar-menu-link-settings',
aiDescription:
'Button to navigate to settings, including account, workspace, and instance settings',
subItems: [
{
- label: 'Account',
+ label: t('sidebar.account'),
href: '#user-settings',
icon: UserCog,
aiId: 'sidebar-menu-link-account',
@@ -402,7 +403,7 @@
...($userStore?.is_admin || $superadmin
? [
{
- label: 'Workspace',
+ label: t('sidebar.workspace'),
href: `${base}/workspace_settings`,
icon: FolderCog,
aiId: 'sidebar-menu-link-workspace',
@@ -414,7 +415,7 @@
...($superadmin
? [
{
- label: 'Instance',
+ label: t('sidebar.instance'),
href: '#superadmin-settings',
icon: ServerCog,
aiId: 'sidebar-menu-link-instance',
@@ -426,7 +427,7 @@
...(!$superadmin && !$userStore?.is_admin
? [
{
- label: 'Leave workspace',
+ label: t('sidebar.leave_workspace'),
action: () => {
leaveWorkspaceModal = true
},
@@ -439,7 +440,7 @@
...($workspaceStore?.startsWith('wm-fork-')
? [
{
- label: 'Delete Forked Workspace',
+ label: t('sidebar.delete_fork'),
action: () => {
deleteWorkspaceForkModal = true
},
@@ -452,7 +453,7 @@
disabled: $userStore?.operator
},
{
- label: 'Workers',
+ label: t('sidebar.workers'),
href: `${base}/workers`,
icon: ServerCog,
disabled: $userStore?.operator,
@@ -460,13 +461,13 @@
aiDescription: 'Button to navigate to workers'
},
{
- label: 'Folders & Groups',
+ label: t('sidebar.folders_groups'),
icon: FolderOpen,
aiId: 'sidebar-menu-link-folders-groups',
aiDescription: 'Button to navigate to folders and groups',
subItems: [
{
- label: 'Folders',
+ label: t('sidebar.folders'),
href: `${base}/folders`,
icon: FolderOpen,
disabled: $userStore?.operator,
@@ -475,7 +476,7 @@
faIcon: undefined
},
{
- label: 'Groups',
+ label: t('sidebar.groups'),
href: `${base}/groups`,
icon: UserCog,
disabled: $userStore?.operator,
@@ -488,13 +489,13 @@
},
$devopsRole || $userStore?.is_admin
? {
- label: 'Logs',
+ label: t('sidebar.logs'),
icon: Logs,
aiId: 'sidebar-menu-link-logs',
aiDescription: 'Button to navigate to logs',
subItems: [
{
- label: 'Audit logs',
+ label: t('sidebar.audit_logs'),
href: `${base}/audit_logs`,
icon: Eye,
aiId: 'sidebar-menu-link-audit-logs',
@@ -503,7 +504,7 @@
...($devopsRole
? [
{
- label: 'Service logs',
+ label: t('sidebar.service_logs'),
href: `${base}/service_logs`,
icon: Logs,
aiId: 'sidebar-menu-link-service-logs',
@@ -514,7 +515,7 @@
...($enterpriseLicense
? [
{
- label: 'Critical alerts',
+ label: t('sidebar.critical_alerts'),
action: () => {
isCriticalAlertsUIOpen.set(true)
},
@@ -528,7 +529,7 @@
]
}
: {
- label: 'Audit logs',
+ label: t('sidebar.audit_logs'),
href: `${base}/audit_logs`,
icon: Eye,
disabled: $userStore?.operator,
@@ -552,7 +553,7 @@
Triggers
{t('sidebar.triggers')}
{#snippet children({ createMenu })}
diff --git a/frontend/src/lib/components/sidebar/UserMenu.svelte b/frontend/src/lib/components/sidebar/UserMenu.svelte
index b6e6d6a9cc..bb3bb03a59 100644
--- a/frontend/src/lib/components/sidebar/UserMenu.svelte
+++ b/frontend/src/lib/components/sidebar/UserMenu.svelte
@@ -11,11 +11,14 @@
import { USER_SETTINGS_HASH } from './settings'
import { isCloudHosted } from '$lib/cloud'
import { twMerge } from 'tailwind-merge'
- import { Crown, ServerCog, LogOut, Moon, Settings, Sun, User } from 'lucide-svelte'
+ import { Crown, ServerCog, LogOut, Moon, Settings, Sun, User, Languages } from 'lucide-svelte'
import DarkModeObserver from '../DarkModeObserver.svelte'
import MenuButton from './MenuButton.svelte'
import { Menu, MenuItem } from '$lib/components/meltComponents'
import { type MenubarBuilders } from '@melt-ui/svelte'
+ import { t } from '$lib/i18n/t.svelte'
+ import { getLocale, setLocale } from '$lib/i18n/t.svelte'
+ import { SUPPORTED_LOCALES, LOCALE_NAMES } from '$lib/i18n/index'
let darkMode: boolean = $state(false)
@@ -53,16 +56,16 @@
{#if $userStore?.is_admin}
- Admin of this workspace
+ {t('user.admin_of_workspace')}
{:else if $userStore?.operator}
- Operator in this workspace
+ {t('user.operator_in_workspace')}
{/if}
{/if}
- Switch theme
+ {t('user.switch_theme')}
+
+
+
@@ -125,7 +141,7 @@
}}
{item}
>
- Upgrade
+ {t('user.upgrade')}
{/if}
{:else}
@@ -136,7 +152,7 @@
}}
{item}
>
- Premium plan
+ {t('user.premium_plan')}
{/if}
diff --git a/frontend/src/lib/i18n/index.ts b/frontend/src/lib/i18n/index.ts
new file mode 100644
index 0000000000..5fbe595a41
--- /dev/null
+++ b/frontend/src/lib/i18n/index.ts
@@ -0,0 +1,14 @@
+export const SUPPORTED_LOCALES = ['en', 'fr', 'de', 'es', 'pt', 'ja', 'zh', 'ko'] as const
+export type Locale = (typeof SUPPORTED_LOCALES)[number]
+export const DEFAULT_LOCALE: Locale = 'en'
+
+export const LOCALE_NAMES: Record = {
+ en: 'English',
+ fr: 'Français',
+ de: 'Deutsch',
+ es: 'Español',
+ pt: 'Português',
+ ja: '日本語',
+ zh: '中文',
+ ko: '한국어'
+}
diff --git a/frontend/src/lib/i18n/messages/de.ts b/frontend/src/lib/i18n/messages/de.ts
new file mode 100644
index 0000000000..9a67e9379e
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/de.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const de: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': 'Start',
+ 'sidebar.runs': 'Ausführungen',
+ 'sidebar.variables': 'Variablen',
+ 'sidebar.resources': 'Ressourcen',
+ 'sidebar.assets': 'Assets',
+ 'sidebar.tutorials': 'Tutorials',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'Trigger',
+ 'sidebar.schedules': 'Zeitpläne',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'E-Mail',
+
+ // Sidebar - settings
+ 'sidebar.settings': 'Einstellungen',
+ 'sidebar.account': 'Konto',
+ 'sidebar.workspace': 'Workspace',
+ 'sidebar.instance': 'Instanz',
+ 'sidebar.leave_workspace': 'Verlassen',
+ 'sidebar.delete_fork': 'Fork löschen',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': 'Ordner & Gruppen',
+ 'sidebar.folders': 'Ordner',
+ 'sidebar.groups': 'Gruppen',
+ 'sidebar.logs': 'Protokolle',
+ 'sidebar.audit_logs': 'Audit',
+ 'sidebar.service_logs': 'Dienste',
+ 'sidebar.critical_alerts': 'Kritische Alerts',
+
+ // Sidebar - help
+ 'sidebar.help': 'Hilfe',
+ 'sidebar.docs': 'Doku',
+ 'sidebar.feedbacks': 'Feedback',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': 'Changelog',
+
+ // User menu
+ 'user.account_settings': 'Kontoeinstellungen',
+ 'user.switch_theme': 'Design wechseln',
+ 'user.sign_out': 'Abmelden',
+ 'user.upgrade': 'Upgrade',
+ 'user.premium_plan': 'Premium-Plan',
+ 'user.admin_of_workspace': 'Admin',
+ 'user.operator_in_workspace': 'Operator',
+ 'user.language': 'Sprache',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'HTTP-Routen',
+ 'operator.websocket_triggers': 'WebSocket-Trigger',
+ 'operator.postgres_triggers': 'Postgres-Trigger',
+ 'operator.kafka_triggers': 'Kafka-Trigger',
+ 'operator.nats_triggers': 'NATS-Trigger',
+ 'operator.sqs_triggers': 'SQS-Trigger',
+ 'operator.gcp_triggers': 'GCP-Trigger',
+ 'operator.mqtt_triggers': 'MQTT-Trigger',
+ 'operator.email_triggers': 'E-Mail-Trigger',
+
+ // Common actions
+ 'action.save': 'Speichern',
+ 'action.cancel': 'Abbrechen',
+ 'action.delete': 'Löschen',
+ 'action.edit': 'Bearbeiten',
+ 'action.create': 'Erstellen',
+ 'action.run': 'Ausführen',
+ 'action.deploy': 'Deployen',
+ 'action.share': 'Teilen',
+ 'action.close': 'Schließen',
+ 'action.confirm': 'Bestätigen',
+ 'action.refresh': 'Aktualisieren',
+
+ // Page headers
+ 'page.home': 'Start',
+ 'page.variables': 'Variablen',
+ 'page.resources': 'Ressourcen',
+ 'page.schedules': 'Zeitpläne',
+ 'page.workers': 'Workers',
+ 'page.folders': 'Ordner',
+ 'page.groups': 'Gruppen'
+}
diff --git a/frontend/src/lib/i18n/messages/en.ts b/frontend/src/lib/i18n/messages/en.ts
new file mode 100644
index 0000000000..ea19ac4689
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/en.ts
@@ -0,0 +1,90 @@
+export const en = {
+ // Sidebar - main nav
+ 'sidebar.home': 'Home',
+ 'sidebar.runs': 'Runs',
+ 'sidebar.variables': 'Variables',
+ 'sidebar.resources': 'Resources',
+ 'sidebar.assets': 'Assets',
+ 'sidebar.tutorials': 'Tutorials',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'Triggers',
+ 'sidebar.schedules': 'Schedules',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'Email',
+
+ // Sidebar - settings
+ 'sidebar.settings': 'Settings',
+ 'sidebar.account': 'Account',
+ 'sidebar.workspace': 'Workspace',
+ 'sidebar.instance': 'Instance',
+ 'sidebar.leave_workspace': 'Leave workspace',
+ 'sidebar.delete_fork': 'Delete Forked Workspace',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': 'Folders & Groups',
+ 'sidebar.folders': 'Folders',
+ 'sidebar.groups': 'Groups',
+ 'sidebar.logs': 'Logs',
+ 'sidebar.audit_logs': 'Audit logs',
+ 'sidebar.service_logs': 'Service logs',
+ 'sidebar.critical_alerts': 'Critical alerts',
+
+ // Sidebar - help
+ 'sidebar.help': 'Help',
+ 'sidebar.docs': 'Docs',
+ 'sidebar.feedbacks': 'Feedbacks',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': 'Changelog',
+
+ // User menu
+ 'user.account_settings': 'Account settings',
+ 'user.switch_theme': 'Switch theme',
+ 'user.sign_out': 'Sign out',
+ 'user.upgrade': 'Upgrade',
+ 'user.premium_plan': 'Premium plan',
+ 'user.admin_of_workspace': 'Admin of this workspace',
+ 'user.operator_in_workspace': 'Operator in this workspace',
+ 'user.language': 'Language',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'Custom HTTP routes',
+ 'operator.websocket_triggers': 'Websocket triggers',
+ 'operator.postgres_triggers': 'Postgres triggers',
+ 'operator.kafka_triggers': 'Kafka triggers',
+ 'operator.nats_triggers': 'NATS triggers',
+ 'operator.sqs_triggers': 'SQS triggers',
+ 'operator.gcp_triggers': 'GCP Pub/Sub triggers',
+ 'operator.mqtt_triggers': 'MQTT triggers',
+ 'operator.email_triggers': 'Email triggers',
+
+ // Common actions
+ 'action.save': 'Save',
+ 'action.cancel': 'Cancel',
+ 'action.delete': 'Delete',
+ 'action.edit': 'Edit',
+ 'action.create': 'Create',
+ 'action.run': 'Run',
+ 'action.deploy': 'Deploy',
+ 'action.share': 'Share',
+ 'action.close': 'Close',
+ 'action.confirm': 'Confirm',
+ 'action.refresh': 'Refresh',
+
+ // Page headers
+ 'page.home': 'Home',
+ 'page.variables': 'Variables',
+ 'page.resources': 'Resources',
+ 'page.schedules': 'Schedules',
+ 'page.workers': 'Workers',
+ 'page.folders': 'Folders',
+ 'page.groups': 'Groups'
+} as const
+
+export type MessageKey = keyof typeof en
diff --git a/frontend/src/lib/i18n/messages/es.ts b/frontend/src/lib/i18n/messages/es.ts
new file mode 100644
index 0000000000..35cfb561ba
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/es.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const es: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': 'Inicio',
+ 'sidebar.runs': 'Ejecuciones',
+ 'sidebar.variables': 'Variables',
+ 'sidebar.resources': 'Recursos',
+ 'sidebar.assets': 'Assets',
+ 'sidebar.tutorials': 'Tutoriales',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'Disparadores',
+ 'sidebar.schedules': 'Programados',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'Email',
+
+ // Sidebar - settings
+ 'sidebar.settings': 'Ajustes',
+ 'sidebar.account': 'Cuenta',
+ 'sidebar.workspace': 'Espacio',
+ 'sidebar.instance': 'Instancia',
+ 'sidebar.leave_workspace': 'Salir',
+ 'sidebar.delete_fork': 'Eliminar fork',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': 'Carpetas y Grupos',
+ 'sidebar.folders': 'Carpetas',
+ 'sidebar.groups': 'Grupos',
+ 'sidebar.logs': 'Registros',
+ 'sidebar.audit_logs': 'Auditoría',
+ 'sidebar.service_logs': 'Servicios',
+ 'sidebar.critical_alerts': 'Alertas críticas',
+
+ // Sidebar - help
+ 'sidebar.help': 'Ayuda',
+ 'sidebar.docs': 'Docs',
+ 'sidebar.feedbacks': 'Comentarios',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': 'Changelog',
+
+ // User menu
+ 'user.account_settings': 'Mi cuenta',
+ 'user.switch_theme': 'Cambiar tema',
+ 'user.sign_out': 'Cerrar sesión',
+ 'user.upgrade': 'Mejorar plan',
+ 'user.premium_plan': 'Plan premium',
+ 'user.admin_of_workspace': 'Admin',
+ 'user.operator_in_workspace': 'Operador',
+ 'user.language': 'Idioma',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'Rutas HTTP',
+ 'operator.websocket_triggers': 'Triggers WebSocket',
+ 'operator.postgres_triggers': 'Triggers Postgres',
+ 'operator.kafka_triggers': 'Triggers Kafka',
+ 'operator.nats_triggers': 'Triggers NATS',
+ 'operator.sqs_triggers': 'Triggers SQS',
+ 'operator.gcp_triggers': 'Triggers GCP',
+ 'operator.mqtt_triggers': 'Triggers MQTT',
+ 'operator.email_triggers': 'Triggers email',
+
+ // Common actions
+ 'action.save': 'Guardar',
+ 'action.cancel': 'Cancelar',
+ 'action.delete': 'Eliminar',
+ 'action.edit': 'Editar',
+ 'action.create': 'Crear',
+ 'action.run': 'Ejecutar',
+ 'action.deploy': 'Desplegar',
+ 'action.share': 'Compartir',
+ 'action.close': 'Cerrar',
+ 'action.confirm': 'Confirmar',
+ 'action.refresh': 'Actualizar',
+
+ // Page headers
+ 'page.home': 'Inicio',
+ 'page.variables': 'Variables',
+ 'page.resources': 'Recursos',
+ 'page.schedules': 'Programados',
+ 'page.workers': 'Workers',
+ 'page.folders': 'Carpetas',
+ 'page.groups': 'Grupos'
+}
diff --git a/frontend/src/lib/i18n/messages/fr.ts b/frontend/src/lib/i18n/messages/fr.ts
new file mode 100644
index 0000000000..b40a9bdac4
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/fr.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const fr: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': 'Accueil',
+ 'sidebar.runs': 'Exécutions',
+ 'sidebar.variables': 'Variables',
+ 'sidebar.resources': 'Ressources',
+ 'sidebar.assets': 'Assets',
+ 'sidebar.tutorials': 'Tutoriels',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'Déclencheurs',
+ 'sidebar.schedules': 'Plannings',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'Email',
+
+ // Sidebar - settings
+ 'sidebar.settings': 'Paramètres',
+ 'sidebar.account': 'Compte',
+ 'sidebar.workspace': 'Espace',
+ 'sidebar.instance': 'Instance',
+ 'sidebar.leave_workspace': 'Quitter',
+ 'sidebar.delete_fork': 'Supprimer le fork',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': 'Dossiers & Groupes',
+ 'sidebar.folders': 'Dossiers',
+ 'sidebar.groups': 'Groupes',
+ 'sidebar.logs': 'Journaux',
+ 'sidebar.audit_logs': 'Audit',
+ 'sidebar.service_logs': 'Services',
+ 'sidebar.critical_alerts': 'Alertes critiques',
+
+ // Sidebar - help
+ 'sidebar.help': 'Aide',
+ 'sidebar.docs': 'Docs',
+ 'sidebar.feedbacks': 'Retours',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': 'Changelog',
+
+ // User menu
+ 'user.account_settings': 'Mon compte',
+ 'user.switch_theme': 'Changer de thème',
+ 'user.sign_out': 'Déconnexion',
+ 'user.upgrade': 'Passer en premium',
+ 'user.premium_plan': 'Plan premium',
+ 'user.admin_of_workspace': 'Admin de cet espace',
+ 'user.operator_in_workspace': 'Opérateur',
+ 'user.language': 'Langue',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'Routes HTTP',
+ 'operator.websocket_triggers': 'Déclencheurs WS',
+ 'operator.postgres_triggers': 'Déclencheurs PG',
+ 'operator.kafka_triggers': 'Déclencheurs Kafka',
+ 'operator.nats_triggers': 'Déclencheurs NATS',
+ 'operator.sqs_triggers': 'Déclencheurs SQS',
+ 'operator.gcp_triggers': 'Déclencheurs GCP',
+ 'operator.mqtt_triggers': 'Déclencheurs MQTT',
+ 'operator.email_triggers': 'Déclencheurs email',
+
+ // Common actions
+ 'action.save': 'Enregistrer',
+ 'action.cancel': 'Annuler',
+ 'action.delete': 'Supprimer',
+ 'action.edit': 'Modifier',
+ 'action.create': 'Créer',
+ 'action.run': 'Lancer',
+ 'action.deploy': 'Déployer',
+ 'action.share': 'Partager',
+ 'action.close': 'Fermer',
+ 'action.confirm': 'Confirmer',
+ 'action.refresh': 'Actualiser',
+
+ // Page headers
+ 'page.home': 'Accueil',
+ 'page.variables': 'Variables',
+ 'page.resources': 'Ressources',
+ 'page.schedules': 'Plannings',
+ 'page.workers': 'Workers',
+ 'page.folders': 'Dossiers',
+ 'page.groups': 'Groupes'
+}
diff --git a/frontend/src/lib/i18n/messages/index.ts b/frontend/src/lib/i18n/messages/index.ts
new file mode 100644
index 0000000000..2d915de831
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/index.ts
@@ -0,0 +1,22 @@
+import type { Locale } from '../index'
+import { en, type MessageKey } from './en'
+import { fr } from './fr'
+import { de } from './de'
+import { es } from './es'
+import { pt } from './pt'
+import { ja } from './ja'
+import { zh } from './zh'
+import { ko } from './ko'
+
+export type { MessageKey }
+
+export const messages: Record> = {
+ en,
+ fr,
+ de,
+ es,
+ pt,
+ ja,
+ zh,
+ ko
+}
diff --git a/frontend/src/lib/i18n/messages/ja.ts b/frontend/src/lib/i18n/messages/ja.ts
new file mode 100644
index 0000000000..16c5ea3479
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/ja.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const ja: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': 'ホーム',
+ 'sidebar.runs': '実行',
+ 'sidebar.variables': '変数',
+ 'sidebar.resources': 'リソース',
+ 'sidebar.assets': 'アセット',
+ 'sidebar.tutorials': 'チュートリアル',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'トリガー',
+ 'sidebar.schedules': 'スケジュール',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'メール',
+
+ // Sidebar - settings
+ 'sidebar.settings': '設定',
+ 'sidebar.account': 'アカウント',
+ 'sidebar.workspace': 'ワークスペース',
+ 'sidebar.instance': 'インスタンス',
+ 'sidebar.leave_workspace': '退出',
+ 'sidebar.delete_fork': 'フォークを削除',
+ 'sidebar.workers': 'ワーカー',
+ 'sidebar.folders_groups': 'フォルダとグループ',
+ 'sidebar.folders': 'フォルダ',
+ 'sidebar.groups': 'グループ',
+ 'sidebar.logs': 'ログ',
+ 'sidebar.audit_logs': '監査ログ',
+ 'sidebar.service_logs': 'サービスログ',
+ 'sidebar.critical_alerts': '重要なアラート',
+
+ // Sidebar - help
+ 'sidebar.help': 'ヘルプ',
+ 'sidebar.docs': 'ドキュメント',
+ 'sidebar.feedbacks': 'フィードバック',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': '変更履歴',
+
+ // User menu
+ 'user.account_settings': 'アカウント設定',
+ 'user.switch_theme': 'テーマ切替',
+ 'user.sign_out': 'ログアウト',
+ 'user.upgrade': 'アップグレード',
+ 'user.premium_plan': 'プレミアムプラン',
+ 'user.admin_of_workspace': '管理者',
+ 'user.operator_in_workspace': 'オペレーター',
+ 'user.language': '言語',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'HTTPルート',
+ 'operator.websocket_triggers': 'WebSocketトリガー',
+ 'operator.postgres_triggers': 'Postgresトリガー',
+ 'operator.kafka_triggers': 'Kafkaトリガー',
+ 'operator.nats_triggers': 'NATSトリガー',
+ 'operator.sqs_triggers': 'SQSトリガー',
+ 'operator.gcp_triggers': 'GCPトリガー',
+ 'operator.mqtt_triggers': 'MQTTトリガー',
+ 'operator.email_triggers': 'メールトリガー',
+
+ // Common actions
+ 'action.save': '保存',
+ 'action.cancel': 'キャンセル',
+ 'action.delete': '削除',
+ 'action.edit': '編集',
+ 'action.create': '作成',
+ 'action.run': '実行',
+ 'action.deploy': 'デプロイ',
+ 'action.share': '共有',
+ 'action.close': '閉じる',
+ 'action.confirm': '確認',
+ 'action.refresh': '更新',
+
+ // Page headers
+ 'page.home': 'ホーム',
+ 'page.variables': '変数',
+ 'page.resources': 'リソース',
+ 'page.schedules': 'スケジュール',
+ 'page.workers': 'ワーカー',
+ 'page.folders': 'フォルダ',
+ 'page.groups': 'グループ'
+}
diff --git a/frontend/src/lib/i18n/messages/ko.ts b/frontend/src/lib/i18n/messages/ko.ts
new file mode 100644
index 0000000000..68e7ab73ba
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/ko.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const ko: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': '홈',
+ 'sidebar.runs': '실행',
+ 'sidebar.variables': '변수',
+ 'sidebar.resources': '리소스',
+ 'sidebar.assets': '에셋',
+ 'sidebar.tutorials': '튜토리얼',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': '트리거',
+ 'sidebar.schedules': '스케줄',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': '이메일',
+
+ // Sidebar - settings
+ 'sidebar.settings': '설정',
+ 'sidebar.account': '계정',
+ 'sidebar.workspace': '워크스페이스',
+ 'sidebar.instance': '인스턴스',
+ 'sidebar.leave_workspace': '나가기',
+ 'sidebar.delete_fork': '포크 삭제',
+ 'sidebar.workers': '워커',
+ 'sidebar.folders_groups': '폴더 및 그룹',
+ 'sidebar.folders': '폴더',
+ 'sidebar.groups': '그룹',
+ 'sidebar.logs': '로그',
+ 'sidebar.audit_logs': '감사 로그',
+ 'sidebar.service_logs': '서비스 로그',
+ 'sidebar.critical_alerts': '심각한 알림',
+
+ // Sidebar - help
+ 'sidebar.help': '도움말',
+ 'sidebar.docs': '문서',
+ 'sidebar.feedbacks': '피드백',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': '변경 내역',
+
+ // User menu
+ 'user.account_settings': '계정 설정',
+ 'user.switch_theme': '테마 전환',
+ 'user.sign_out': '로그아웃',
+ 'user.upgrade': '업그레이드',
+ 'user.premium_plan': '프리미엄 플랜',
+ 'user.admin_of_workspace': '관리자',
+ 'user.operator_in_workspace': '운영자',
+ 'user.language': '언어',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'HTTP 라우트',
+ 'operator.websocket_triggers': 'WebSocket 트리거',
+ 'operator.postgres_triggers': 'Postgres 트리거',
+ 'operator.kafka_triggers': 'Kafka 트리거',
+ 'operator.nats_triggers': 'NATS 트리거',
+ 'operator.sqs_triggers': 'SQS 트리거',
+ 'operator.gcp_triggers': 'GCP 트리거',
+ 'operator.mqtt_triggers': 'MQTT 트리거',
+ 'operator.email_triggers': '이메일 트리거',
+
+ // Common actions
+ 'action.save': '저장',
+ 'action.cancel': '취소',
+ 'action.delete': '삭제',
+ 'action.edit': '편집',
+ 'action.create': '생성',
+ 'action.run': '실행',
+ 'action.deploy': '배포',
+ 'action.share': '공유',
+ 'action.close': '닫기',
+ 'action.confirm': '확인',
+ 'action.refresh': '새로고침',
+
+ // Page headers
+ 'page.home': '홈',
+ 'page.variables': '변수',
+ 'page.resources': '리소스',
+ 'page.schedules': '스케줄',
+ 'page.workers': '워커',
+ 'page.folders': '폴더',
+ 'page.groups': '그룹'
+}
diff --git a/frontend/src/lib/i18n/messages/pt.ts b/frontend/src/lib/i18n/messages/pt.ts
new file mode 100644
index 0000000000..b648c12baa
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/pt.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const pt: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': 'Início',
+ 'sidebar.runs': 'Execuções',
+ 'sidebar.variables': 'Variáveis',
+ 'sidebar.resources': 'Recursos',
+ 'sidebar.assets': 'Assets',
+ 'sidebar.tutorials': 'Tutoriais',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': 'Gatilhos',
+ 'sidebar.schedules': 'Agendamentos',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': 'Email',
+
+ // Sidebar - settings
+ 'sidebar.settings': 'Configurações',
+ 'sidebar.account': 'Conta',
+ 'sidebar.workspace': 'Workspace',
+ 'sidebar.instance': 'Instância',
+ 'sidebar.leave_workspace': 'Sair',
+ 'sidebar.delete_fork': 'Excluir fork',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': 'Pastas e Grupos',
+ 'sidebar.folders': 'Pastas',
+ 'sidebar.groups': 'Grupos',
+ 'sidebar.logs': 'Logs',
+ 'sidebar.audit_logs': 'Auditoria',
+ 'sidebar.service_logs': 'Serviços',
+ 'sidebar.critical_alerts': 'Alertas críticos',
+
+ // Sidebar - help
+ 'sidebar.help': 'Ajuda',
+ 'sidebar.docs': 'Docs',
+ 'sidebar.feedbacks': 'Feedbacks',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': 'Changelog',
+
+ // User menu
+ 'user.account_settings': 'Minha conta',
+ 'user.switch_theme': 'Mudar tema',
+ 'user.sign_out': 'Sair',
+ 'user.upgrade': 'Upgrade',
+ 'user.premium_plan': 'Plano premium',
+ 'user.admin_of_workspace': 'Admin',
+ 'user.operator_in_workspace': 'Operador',
+ 'user.language': 'Idioma',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'Rotas HTTP',
+ 'operator.websocket_triggers': 'Triggers WebSocket',
+ 'operator.postgres_triggers': 'Triggers Postgres',
+ 'operator.kafka_triggers': 'Triggers Kafka',
+ 'operator.nats_triggers': 'Triggers NATS',
+ 'operator.sqs_triggers': 'Triggers SQS',
+ 'operator.gcp_triggers': 'Triggers GCP',
+ 'operator.mqtt_triggers': 'Triggers MQTT',
+ 'operator.email_triggers': 'Triggers email',
+
+ // Common actions
+ 'action.save': 'Salvar',
+ 'action.cancel': 'Cancelar',
+ 'action.delete': 'Excluir',
+ 'action.edit': 'Editar',
+ 'action.create': 'Criar',
+ 'action.run': 'Executar',
+ 'action.deploy': 'Implantar',
+ 'action.share': 'Compartilhar',
+ 'action.close': 'Fechar',
+ 'action.confirm': 'Confirmar',
+ 'action.refresh': 'Atualizar',
+
+ // Page headers
+ 'page.home': 'Início',
+ 'page.variables': 'Variáveis',
+ 'page.resources': 'Recursos',
+ 'page.schedules': 'Agendamentos',
+ 'page.workers': 'Workers',
+ 'page.folders': 'Pastas',
+ 'page.groups': 'Grupos'
+}
diff --git a/frontend/src/lib/i18n/messages/zh.ts b/frontend/src/lib/i18n/messages/zh.ts
new file mode 100644
index 0000000000..c87b25f6fb
--- /dev/null
+++ b/frontend/src/lib/i18n/messages/zh.ts
@@ -0,0 +1,90 @@
+import type { MessageKey } from './en'
+
+export const zh: Record = {
+ // Sidebar - main nav
+ 'sidebar.home': '首页',
+ 'sidebar.runs': '运行',
+ 'sidebar.variables': '变量',
+ 'sidebar.resources': '资源',
+ 'sidebar.assets': '资产',
+ 'sidebar.tutorials': '教程',
+
+ // Sidebar - triggers section
+ 'sidebar.triggers': '触发器',
+ 'sidebar.schedules': '定时任务',
+ 'sidebar.http': 'HTTP',
+ 'sidebar.websockets': 'WebSockets',
+ 'sidebar.postgres': 'Postgres',
+ 'sidebar.kafka': 'Kafka',
+ 'sidebar.nats': 'NATS',
+ 'sidebar.sqs': 'SQS',
+ 'sidebar.gcp_pubsub': 'GCP Pub/Sub',
+ 'sidebar.mqtt': 'MQTT',
+ 'sidebar.email': '邮件',
+
+ // Sidebar - settings
+ 'sidebar.settings': '设置',
+ 'sidebar.account': '账户',
+ 'sidebar.workspace': '工作区',
+ 'sidebar.instance': '实例',
+ 'sidebar.leave_workspace': '退出',
+ 'sidebar.delete_fork': '删除分支',
+ 'sidebar.workers': 'Workers',
+ 'sidebar.folders_groups': '文件夹和分组',
+ 'sidebar.folders': '文件夹',
+ 'sidebar.groups': '分组',
+ 'sidebar.logs': '日志',
+ 'sidebar.audit_logs': '审计日志',
+ 'sidebar.service_logs': '服务日志',
+ 'sidebar.critical_alerts': '严重告警',
+
+ // Sidebar - help
+ 'sidebar.help': '帮助',
+ 'sidebar.docs': '文档',
+ 'sidebar.feedbacks': '反馈',
+ 'sidebar.issues': 'Issues',
+ 'sidebar.changelog': '更新日志',
+
+ // User menu
+ 'user.account_settings': '账户设置',
+ 'user.switch_theme': '切换主题',
+ 'user.sign_out': '退出登录',
+ 'user.upgrade': '升级',
+ 'user.premium_plan': '高级版',
+ 'user.admin_of_workspace': '管理员',
+ 'user.operator_in_workspace': '操作员',
+ 'user.language': '语言',
+
+ // Operator menu
+ 'operator.custom_http_routes': 'HTTP路由',
+ 'operator.websocket_triggers': 'WebSocket触发器',
+ 'operator.postgres_triggers': 'Postgres触发器',
+ 'operator.kafka_triggers': 'Kafka触发器',
+ 'operator.nats_triggers': 'NATS触发器',
+ 'operator.sqs_triggers': 'SQS触发器',
+ 'operator.gcp_triggers': 'GCP触发器',
+ 'operator.mqtt_triggers': 'MQTT触发器',
+ 'operator.email_triggers': '邮件触发器',
+
+ // Common actions
+ 'action.save': '保存',
+ 'action.cancel': '取消',
+ 'action.delete': '删除',
+ 'action.edit': '编辑',
+ 'action.create': '创建',
+ 'action.run': '运行',
+ 'action.deploy': '部署',
+ 'action.share': '共享',
+ 'action.close': '关闭',
+ 'action.confirm': '确认',
+ 'action.refresh': '刷新',
+
+ // Page headers
+ 'page.home': '首页',
+ 'page.variables': '变量',
+ 'page.resources': '资源',
+ 'page.schedules': '定时任务',
+ 'page.workers': 'Workers',
+ 'page.folders': '文件夹',
+ 'page.groups': '分组'
+}
diff --git a/frontend/src/lib/i18n/t.svelte.ts b/frontend/src/lib/i18n/t.svelte.ts
new file mode 100644
index 0000000000..da68dd3ae2
--- /dev/null
+++ b/frontend/src/lib/i18n/t.svelte.ts
@@ -0,0 +1,33 @@
+import { getLocalSetting, storeLocalSetting } from '$lib/utils'
+import { type Locale, DEFAULT_LOCALE, SUPPORTED_LOCALES } from './index'
+import { en, type MessageKey } from './messages/en'
+import { messages } from './messages/index'
+
+function getInitialLocale(): Locale {
+ const stored = getLocalSetting('locale')
+ if (stored && SUPPORTED_LOCALES.includes(stored as Locale)) {
+ return stored as Locale
+ }
+ return DEFAULT_LOCALE
+}
+
+let locale = $state(getInitialLocale())
+
+export function getLocale(): Locale {
+ return locale
+}
+
+export function setLocale(l: Locale) {
+ locale = l
+ storeLocalSetting('locale', l)
+ if (typeof document !== 'undefined') {
+ document.documentElement.lang = l
+ }
+}
+
+export function t(key: MessageKey): string {
+ if (locale === 'en') {
+ return en[key]
+ }
+ return messages[locale]?.[key] ?? en[key]
+}