diff --git a/packages/i18n/src/locales/en/core.json b/packages/i18n/src/locales/en/core.json new file mode 100644 index 00000000000..889eb3b1d6f --- /dev/null +++ b/packages/i18n/src/locales/en/core.json @@ -0,0 +1,172 @@ +{ + "sidebar": { + "projects": "Projects", + "pages": "Pages", + "new_work_item": "New work item", + "home": "Home", + "your_work": "Your work", + "inbox": "Inbox", + "workspace": "Workspace", + "views": "Views", + "analytics": "Analytics", + "your_projects": "Your projects", + "work_items": "Work items", + "cycles": "Cycles", + "modules": "Modules", + "intake": "Intake", + "drafts": "Drafts", + "your_favorites": "Your favorites", + "pro": "Pro", + "upgrade": "Upgrade" + }, + + "auth": { + "common": { + "email": { + "label": "Email", + "placeholder": "name@company.com", + "errors": { + "required": "Email is required", + "invalid": "Email is invalid" + } + }, + "password": { + "label": "Password", + "set_password": "Set a password", + "placeholder": "Enter password", + "confirm_password": { + "label": "Confirm password", + "placeholder": "Confirm password" + }, + "current_password": { + "label": "Current password" + }, + "new_password": { + "label": "New password", + "placeholder": "Enter new password" + }, + "change_password": { + "label": { + "default": "Change password", + "submitting": "Changing password" + } + }, + "errors": { + "match": "Passwords don't match", + "empty": "Please enter your password", + "length": "Password length should me more than 8 characters", + "strength": { + "weak": "Password is weak", + "strong": "Password is strong" + } + }, + "submit": "Set password", + "toast": { + "change_password": { + "success": { + "title": "Success!", + "message": "Password changed successfully." + }, + "error": { + "title": "Error!", + "message": "Something went wrong. Please try again." + } + } + } + }, + "unique_code": { + "label": "Unique code", + "placeholder": "gets-sets-flys", + "paste_code": "Paste the code sent to your email", + "requesting_new_code": "Requesting new code", + "sending_code": "Sending code" + }, + "already_have_an_account": "Already have an account?", + "login": "Log in", + "create_account": "Create an account", + "new_to_plane": "New to Plane?", + "back_to_sign_in": "Back to sign in", + "resend_in": "Resend in {seconds} seconds", + "sign_in_with_unique_code": "Sign in with unique code", + "forgot_password": "Forgot your password?" + }, + "sign_up": { + "header": { + "label": "Create an account to start managing work with your team.", + "step": { + "email": { + "header": "Sign up", + "sub_header": "" + }, + "password": { + "header": "Sign up", + "sub_header": "Sign up using an email-password combination." + }, + "unique_code": { + "header": "Sign up", + "sub_header": "Sign up using a unique code sent to the email address above." + } + } + }, + "errors": { + "password": { + "strength": "Try setting-up a strong password to proceed" + } + } + }, + "sign_in": { + "header": { + "label": "Log in to start managing work with your team.", + "step": { + "email": { + "header": "Log in or sign up", + "sub_header": "" + }, + "password": { + "header": "Log in or sign up", + "sub_header": "Use your email-password combination to log in." + }, + "unique_code": { + "header": "Log in or sign up", + "sub_header": "Log in using a unique code sent to the email address above." + } + } + } + }, + "forgot_password": { + "title": "Reset your password", + "description": "Enter your user account's verified email address and we will send you a password reset link.", + "email_sent": "We sent the reset link to your email address", + "send_reset_link": "Send reset link", + "errors": { + "smtp_not_enabled": "We see that your god hasn't enabled SMTP, we will not be able to send a password reset link" + }, + "toast": { + "success": { + "title": "Email sent", + "message": "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder." + }, + "error": { + "title": "Error!", + "message": "Something went wrong. Please try again." + } + } + }, + "reset_password": { + "title": "Set new password", + "description": "Secure your account with a strong password" + }, + "set_password": { + "title": "Secure your account", + "description": "Setting password helps you login securely" + }, + "sign_out": { + "toast": { + "error": { + "title": "Error!", + "message": "Failed to sign out. Please try again." + } + } + } + } +} diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index b3877581ddd..e2ddea60ce4 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -2076,155 +2076,8 @@ } }, - "auth": { - "common": { - "email": { - "label": "Email", - "placeholder": "name@company.com", - "errors": { - "required": "Email is required", - "invalid": "Email is invalid" - } - }, - "password": { - "label": "Password", - "set_password": "Set a password", - "placeholder": "Enter password", - "confirm_password": { - "label": "Confirm password", - "placeholder": "Confirm password" - }, - "current_password": { - "label": "Current password" - }, - "new_password": { - "label": "New password", - "placeholder": "Enter new password" - }, - "change_password": { - "label": { - "default": "Change password", - "submitting": "Changing password" - } - }, - "errors": { - "match": "Passwords don't match", - "empty": "Please enter your password", - "length": "Password length should me more than 8 characters", - "strength": { - "weak": "Password is weak", - "strong": "Password is strong" - } - }, - "submit": "Set password", - "toast": { - "change_password": { - "success": { - "title": "Success!", - "message": "Password changed successfully." - }, - "error": { - "title": "Error!", - "message": "Something went wrong. Please try again." - } - } - } - }, - "unique_code": { - "label": "Unique code", - "placeholder": "gets-sets-flys", - "paste_code": "Paste the code sent to your email", - "requesting_new_code": "Requesting new code", - "sending_code": "Sending code" - }, - "already_have_an_account": "Already have an account?", - "login": "Log in", - "create_account": "Create an account", - "new_to_plane": "New to Plane?", - "back_to_sign_in": "Back to sign in", - "resend_in": "Resend in {seconds} seconds", - "sign_in_with_unique_code": "Sign in with unique code", - "forgot_password": "Forgot your password?" - }, - "sign_up": { - "header": { - "label": "Create an account to start managing work with your team.", - "step": { - "email": { - "header": "Sign up", - "sub_header": "" - }, - "password": { - "header": "Sign up", - "sub_header": "Sign up using an email-password combination." - }, - "unique_code": { - "header": "Sign up", - "sub_header": "Sign up using a unique code sent to the email address above." - } - } - }, - "errors": { - "password": { - "strength": "Try setting-up a strong password to proceed" - } - } - }, - "sign_in": { - "header": { - "label": "Log in to start managing work with your team.", - "step": { - "email": { - "header": "Log in or sign up", - "sub_header": "" - }, - "password": { - "header": "Log in or sign up", - "sub_header": "Use your email-password combination to log in." - }, - "unique_code": { - "header": "Log in or sign up", - "sub_header": "Log in using a unique code sent to the email address above." - } - } - } - }, - "forgot_password": { - "title": "Reset your password", - "description": "Enter your user account's verified email address and we will send you a password reset link.", - "email_sent": "We sent the reset link to your email address", - "send_reset_link": "Send reset link", - "errors": { - "smtp_not_enabled": "We see that your god hasn't enabled SMTP, we will not be able to send a password reset link" - }, - "toast": { - "success": { - "title": "Email sent", - "message": "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder." - }, - "error": { - "title": "Error!", - "message": "Something went wrong. Please try again." - } - } - }, - "reset_password": { - "title": "Set new password", - "description": "Secure your account with a strong password" - }, - "set_password": { - "title": "Secure your account", - "description": "Setting password helps you login securely" - }, - "sign_out": { - "toast": { - "error": { - "title": "Error!", - "message": "Failed to sign out. Please try again." - } - } - } - }, + + "cycle": { "label": "{count, plural, one {Cycle} other {Cycles}}", diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 17873b42b86..aeff7f6efb1 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -332,6 +332,27 @@ "re_generate_key": "Regenerar clave", "export": "Exportar", + "sidebar": { + "projects": "Proyectos", + "pages": "Páginas", + "new_work_item": "Nuevo elemento de trabajo", + "home": "Inicio", + "your_work": "Tu trabajo", + "inbox": "Bandeja de entrada", + "workspace": "Espacio de trabajo", + "views": "Vistas", + "analytics": "Análisis", + "your_projects": "Tus proyectos", + "work_items": "Elementos de trabajo", + "cycles": "Ciclos", + "modules": "Módulos", + "intake": "Entrada", + "drafts": "Borradores", + "your_favorites": "Tus favoritos", + "pro": "Pro", + "upgrade": "Actualizar" + }, + "project_view": { "sort_by": { "created_at": "Fecha de creación", diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 5db24edf6d6..a323155cfc8 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -332,6 +332,27 @@ "re_generate_key": "Régénérer la clé", "export": "Exporter", + "sidebar": { + "projects": "Projets", + "pages": "Pages", + "new_work_item": "Nouvel élément de travail", + "home": "Accueil", + "your_work": "Votre travail", + "inbox": "Boîte de réception", + "workspace": "Espace de travail", + "views": "Vues", + "analytics": "Analytique", + "your_projects": "Vos projets", + "work_items": "Éléments de travail", + "cycles": "Cycles", + "modules": "Modules", + "intake": "Réception", + "drafts": "Brouillons", + "your_favorites": "Vos favoris", + "pro": "Pro", + "upgrade": "Mettre à niveau" + }, + "project_view": { "sort_by": { "created_at": "Créé le", diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index e7226961b5c..932c7ea0acd 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -332,6 +332,28 @@ "re_generate_key": "キーを再生成", "export": "エクスポート", + "sidebar": { + "projects": "プロジェクト", + "pages": "ページ", + "new_work_item": "新規作業項目", + "home": "ホーム", + "your_work": "あなたの作業", + "inbox": "受信トレイ", + "workspace": "ワークスペース", + "views": "ビュー", + "analytics": "分析", + "your_projects": "あなたのプロジェクト", + "work_items": "作業項目", + "cycles": "サイクル", + "modules": "モジュール", + "intake": "取り込み", + "drafts": "下書き", + "your_favorites": "お気に入り", + "pro": "Pro", + "upgrade": "アップグレード" + }, + + "project_view": { "sort_by": { "created_at": "作成日時", diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index 4f616a49fa4..5281d8c9f40 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -332,6 +332,29 @@ "re_generate_key": "重新生成密钥", "export": "导出", + "sidebar": { + "projects": "项目", + "pages": "页面", + "new_work_item": "新建工作项", + "home": "首页", + "your_work": "你的工作", + "inbox": "收件箱", + "workspace": "工作空间", + "views": "视图", + "analytics": "分析", + "your_projects": "你的项目", + "work_items": "工作项", + "cycles": "周期", + "modules": "模块", + "intake": "采集", + "business": "商务", + "drafts": "草稿", + "your_favorites": "收藏夹", + "pro": "Pro", + "upgrade": "升级" + }, + + "project_view": { "sort_by": { "created_at": "创建于", diff --git a/packages/i18n/src/store/index.ts b/packages/i18n/src/store/index.ts index d5bd3e598d6..78e1311adbf 100644 --- a/packages/i18n/src/store/index.ts +++ b/packages/i18n/src/store/index.ts @@ -1,8 +1,11 @@ import IntlMessageFormat from "intl-messageformat"; import get from "lodash/get"; -import { makeAutoObservable } from "mobx"; +import merge from "lodash/merge"; +import { makeAutoObservable, runInAction } from "mobx"; // constants import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, STORAGE_KEY } from "../constants"; +// core translations imports +import coreEn from "../locales/en/core.json"; // types import { TLanguage, ILanguageOption, ITranslations } from "../types"; @@ -12,42 +15,35 @@ import { TLanguage, ILanguageOption, ITranslations } from "../types"; * Uses IntlMessageFormat to format the translations */ export class TranslationStore { + // Core translations that are always loaded + private coreTranslations: ITranslations = { + en: coreEn, + }; // List of translations for each language private translations: ITranslations = {}; // Cache for IntlMessageFormat instances private messageCache: Map = new Map(); // Current language currentLocale: TLanguage = FALLBACK_LANGUAGE; + // Loading state + isLoading: boolean = true; + isInitialized: boolean = false; + // Set of loaded languages + private loadedLanguages: Set = new Set(); /** * Constructor for the TranslationStore class */ constructor() { makeAutoObservable(this); + // Initialize with core translations immediately + this.translations = this.coreTranslations; + // Initialize language this.initializeLanguage(); + // Load all the translations this.loadTranslations(); } - /** - * Loads translations from JSON files and initializes the message cache - */ - private async loadTranslations() { - try { - // dynamic import of translations - const translations = { - en: (await import("../locales/en/translations.json")).default, - fr: (await import("../locales/fr/translations.json")).default, - es: (await import("../locales/es/translations.json")).default, - ja: (await import("../locales/ja/translations.json")).default, - "zh-CN": (await import("../locales/zh-CN/translations.json")).default, - }; - this.translations = translations; - this.messageCache.clear(); // Clear cache when translations change - } catch (error) { - console.error("Failed to load translations:", error); - } - } - /** Initializes the language based on the local storage or browser language */ private initializeLanguage() { if (typeof window === "undefined") return; @@ -62,6 +58,100 @@ export class TranslationStore { this.setLanguage(browserLang); } + /** Loads the translations for the current language */ + private async loadTranslations(): Promise { + try { + // Set initialized to true (Core translations are already loaded) + runInAction(() => { + this.isInitialized = true; + }); + // Load current and fallback languages in parallel + await this.loadPrimaryLanguages(); + // Load all remaining languages in parallel + this.loadRemainingLanguages(); + } catch (error) { + console.error("Failed in translation initialization:", error); + runInAction(() => { + this.isLoading = false; + }); + } + } + + private async loadPrimaryLanguages(): Promise { + try { + // Load current and fallback languages in parallel + const languagesToLoad = new Set([this.currentLocale]); + // Add fallback language only if different from current + if (this.currentLocale !== FALLBACK_LANGUAGE) { + languagesToLoad.add(FALLBACK_LANGUAGE); + } + // Load all primary languages in parallel + const loadPromises = Array.from(languagesToLoad).map((lang) => this.loadLanguageTranslations(lang)); + await Promise.all(loadPromises); + // Update loading state + runInAction(() => { + this.isLoading = false; + }); + } catch (error) { + console.error("Failed to load primary languages:", error); + runInAction(() => { + this.isLoading = false; + }); + } + } + + private loadRemainingLanguages(): void { + const remainingLanguages = SUPPORTED_LANGUAGES.map((lang) => lang.value).filter( + (lang) => + !this.loadedLanguages.has(lang as TLanguage) && lang !== this.currentLocale && lang !== FALLBACK_LANGUAGE + ); + // Load all remaining languages in parallel + Promise.all(remainingLanguages.map((lang) => this.loadLanguageTranslations(lang as TLanguage))).catch((error) => { + console.error("Failed to load some remaining languages:", error); + }); + } + + private async loadLanguageTranslations(language: TLanguage): Promise { + // Skip if already loaded + if (this.loadedLanguages.has(language)) return; + + try { + const translations = await this.importLanguageFile(language); + runInAction(() => { + // Use lodash merge for deep merging + this.translations[language] = merge({}, this.coreTranslations[language] || {}, translations.default); + // Add to loaded languages + this.loadedLanguages.add(language); + // Clear cache + this.messageCache.clear(); + }); + } catch (error) { + console.error(`Failed to load translations for ${language}:`, error); + } + } + + /** + * Imports the translations for the given language + * @param language - The language to import the translations for + * @returns {Promise} + */ + private importLanguageFile(language: TLanguage): Promise { + switch (language) { + case "en": + return import("../locales/en/translations.json"); + case "fr": + return import("../locales/fr/translations.json"); + case "es": + return import("../locales/es/translations.json"); + case "ja": + return import("../locales/ja/translations.json"); + case "zh-CN": + return import("../locales/zh-CN/translations.json"); + default: + throw new Error(`Unsupported language: ${language}`); + } + } + /** Checks if the language is valid based on the supported languages */ private isValidLanguage(lang: string | null): lang is TLanguage { return lang !== null && this.availableLanguages.some((l) => l.value === lang); @@ -173,20 +263,26 @@ export class TranslationStore { * Sets the current language and updates the translations * @param lng - The new language */ - setLanguage(lng: TLanguage): void { + async setLanguage(lng: TLanguage): Promise { try { if (!this.isValidLanguage(lng)) { throw new Error(`Invalid language: ${lng}`); } - if (typeof window !== "undefined") { - localStorage.setItem(STORAGE_KEY, lng); + // Safeguard in case background loading failed + if (!this.loadedLanguages.has(lng)) { + await this.loadLanguageTranslations(lng); } - this.currentLocale = lng; - this.messageCache.clear(); // Clear cache when language changes + if (typeof window !== "undefined") { + localStorage.setItem(STORAGE_KEY, lng); document.documentElement.lang = lng; } + + runInAction(() => { + this.currentLocale = lng; + this.messageCache.clear(); // Clear cache when language changes + }); } catch (error) { console.error("Failed to set language:", error); } diff --git a/web/app/accounts/forgot-password/page.tsx b/web/app/accounts/forgot-password/page.tsx index 7bd8a0f4cc8..22a241db659 100644 --- a/web/app/accounts/forgot-password/page.tsx +++ b/web/app/accounts/forgot-password/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; @@ -39,7 +40,7 @@ const defaultValues: TForgotPasswordFormValues = { // services const authService = new AuthService(); -export default function ForgotPasswordPage() { +const ForgotPasswordPage = observer(() => { // search params const searchParams = useSearchParams(); const email = searchParams.get("email"); @@ -187,4 +188,6 @@ export default function ForgotPasswordPage() { ); -} +}); + +export default ForgotPasswordPage; diff --git a/web/app/accounts/reset-password/page.tsx b/web/app/accounts/reset-password/page.tsx index e874546ea4c..e0230f205f5 100644 --- a/web/app/accounts/reset-password/page.tsx +++ b/web/app/accounts/reset-password/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; @@ -46,7 +47,7 @@ const defaultValues: TResetPasswordFormValues = { // services const authService = new AuthService(); -export default function ResetPasswordPage() { +const ResetPasswordPage = observer(() => { // search params const searchParams = useSearchParams(); const uidb64 = searchParams.get("uidb64"); @@ -236,4 +237,6 @@ export default function ResetPasswordPage() { ); -} +}); + +export default ResetPasswordPage; diff --git a/web/ce/components/workspace/edition-badge.tsx b/web/ce/components/workspace/edition-badge.tsx index 82d4f47d244..effb5501b4b 100644 --- a/web/ce/components/workspace/edition-badge.tsx +++ b/web/ce/components/workspace/edition-badge.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import packageJson from "package.json"; import { useTranslation } from "@plane/i18n"; // ui import { Button, Tooltip } from "@plane/ui"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; // assets -import packageJson from "package.json"; // local components import { PaidPlanUpgradeModal } from "./upgrade"; @@ -29,7 +29,7 @@ export const WorkspaceEditionBadge = observer(() => { className="w-fit min-w-24 cursor-pointer rounded-2xl px-2 py-1 text-center text-sm font-medium outline-none" onClick={() => setIsPaidPlanPurchaseModalOpen(true)} > - {t("upgrade")} + {t("sidebar.upgrade")} diff --git a/web/ce/components/workspace/upgrade-badge.tsx b/web/ce/components/workspace/upgrade-badge.tsx index 3fc3654cfba..1abb731e7ff 100644 --- a/web/ce/components/workspace/upgrade-badge.tsx +++ b/web/ce/components/workspace/upgrade-badge.tsx @@ -1,5 +1,6 @@ import { FC } from "react"; // helpers +import { useTranslation } from "@plane/i18n"; import { cn } from "@/helpers/common.helper"; type TUpgradeBadge = { @@ -10,6 +11,8 @@ type TUpgradeBadge = { export const UpgradeBadge: FC = (props) => { const { className, size = "sm" } = props; + const { t } = useTranslation(); + return (
= (props) => { className )} > - Pro + {t("sidebar.pro")}
); }; diff --git a/web/core/components/account/auth-forms/auth-header.tsx b/web/core/components/account/auth-forms/auth-header.tsx index 132fb094792..b609fe0a5c5 100644 --- a/web/core/components/account/auth-forms/auth-header.tsx +++ b/web/core/components/account/auth-forms/auth-header.tsx @@ -1,4 +1,5 @@ import { FC, ReactNode } from "react"; +import { observer } from "mobx-react"; import useSWR from "swr"; import { useTranslation } from "@plane/i18n"; import { IWorkspaceMemberInvitation } from "@plane/types"; @@ -52,7 +53,7 @@ const Titles = { const workSpaceService = new WorkspaceService(); -export const AuthHeader: FC = (props) => { +export const AuthHeader: FC = observer((props) => { const { workspaceSlug, invitationId, invitationEmail, authMode, currentAuthStep, children } = props; // plane imports const { t } = useTranslation(); @@ -109,4 +110,4 @@ export const AuthHeader: FC = (props) => { {children} ); -}; +}); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index e1edc361521..8786e8d2b13 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -200,7 +200,7 @@ export const SidebarFavoritesMenu = observer(() => { )} > toggleFavoriteMenu(!isFavoriteMenuOpen)} className="flex-1 text-start"> - {t("your_favorites").toUpperCase()} + {t("sidebar.your_favorites").toUpperCase()} diff --git a/web/core/components/workspace/sidebar/project-navigation.tsx b/web/core/components/workspace/sidebar/project-navigation.tsx index 0053f4b4c46..e859eed41e6 100644 --- a/web/core/components/workspace/sidebar/project-navigation.tsx +++ b/web/core/components/workspace/sidebar/project-navigation.tsx @@ -17,13 +17,13 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; // plane-web constants export type TNavigationItem = { - key: string; name: string; href: string; icon: React.ElementType; access: EUserPermissions[] | EUserProjectRoles[]; shouldRender: boolean; sortOrder: number; + i18n_key: string; }; type TProjectItemsProps = { @@ -56,7 +56,7 @@ export const ProjectNavigation: FC = observer((props) => { const baseNavigation = useCallback( (workspaceSlug: string, projectId: string): TNavigationItem[] => [ { - key: "issues", + i18n_key: "sidebar.work_items", name: "Work items", href: `/${workspaceSlug}/projects/${projectId}/issues`, icon: LayersIcon, @@ -65,7 +65,7 @@ export const ProjectNavigation: FC = observer((props) => { sortOrder: 1, }, { - key: "cycles", + i18n_key: "sidebar.cycles", name: "Cycles", href: `/${workspaceSlug}/projects/${projectId}/cycles`, icon: ContrastIcon, @@ -74,7 +74,7 @@ export const ProjectNavigation: FC = observer((props) => { sortOrder: 2, }, { - key: "modules", + i18n_key: "sidebar.modules", name: "Modules", href: `/${workspaceSlug}/projects/${projectId}/modules`, icon: DiceIcon, @@ -83,7 +83,7 @@ export const ProjectNavigation: FC = observer((props) => { sortOrder: 3, }, { - key: "views", + i18n_key: "sidebar.views", name: "Views", href: `/${workspaceSlug}/projects/${projectId}/views`, icon: Layers, @@ -92,7 +92,7 @@ export const ProjectNavigation: FC = observer((props) => { sortOrder: 4, }, { - key: "pages", + i18n_key: "sidebar.pages", name: "Pages", href: `/${workspaceSlug}/projects/${projectId}/pages`, icon: FileText, @@ -101,7 +101,7 @@ export const ProjectNavigation: FC = observer((props) => { sortOrder: 5, }, { - key: "intake", + i18n_key: "sidebar.intake", name: "Intake", href: `/${workspaceSlug}/projects/${projectId}/inbox`, icon: Intake, @@ -145,7 +145,7 @@ export const ProjectNavigation: FC = observer((props) => { = observer((props) => { - {!isSidebarCollapsed && {t(item.key)}} + {!isSidebarCollapsed && {t(item.i18n_key)}} diff --git a/web/core/components/workspace/sidebar/projects-list.tsx b/web/core/components/workspace/sidebar/projects-list.tsx index 6ca6b525fa3..847ba914af3 100644 --- a/web/core/components/workspace/sidebar/projects-list.tsx +++ b/web/core/components/workspace/sidebar/projects-list.tsx @@ -187,7 +187,7 @@ export const SidebarProjectsList: FC = observer(() => { {isCollapsed ? ( ) : ( - {t("your_projects").toUpperCase()} + {t("sidebar.your_projects").toUpperCase()} )} diff --git a/web/core/components/workspace/sidebar/quick-actions.tsx b/web/core/components/workspace/sidebar/quick-actions.tsx index 8e497d7a137..c0c8eee9c78 100644 --- a/web/core/components/workspace/sidebar/quick-actions.tsx +++ b/web/core/components/workspace/sidebar/quick-actions.tsx @@ -94,7 +94,9 @@ export const SidebarQuickActions = observer(() => { disabled={disabled} > - {!isSidebarCollapsed && {t("new_issue")}} + {!isSidebarCollapsed && ( + {t("sidebar.new_work_item")} + )}