-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[WEB-2678]feat: added functionality to add labels directly from dropdown #6208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
00c15da
8b178a5
f00a7c6
fd1011c
6172c64
d38b637
afd2d6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| build/* | ||
| dist/* | ||
| out/* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /** @type {import("eslint").Linter.Config} */ | ||
| module.exports = { | ||
| root: true, | ||
| extends: ["@plane/eslint-config/library.js"], | ||
| parser: "@typescript-eslint/parser", | ||
| parserOptions: { | ||
| project: true, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .turbo | ||
| out/ | ||
| dist/ | ||
| build/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "printWidth": 120, | ||
| "tabWidth": 2, | ||
| "trailingComma": "es5" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "@plane/i18n", | ||
| "version": "0.24.1", | ||
| "description": "I18n shared across multiple apps internally", | ||
| "private": true, | ||
| "main": "./src/index.ts", | ||
| "types": "./src/index.ts", | ||
| "scripts": { | ||
| "lint": "eslint src --ext .ts,.tsx", | ||
| "lint:errors": "eslint src --ext .ts,.tsx --quiet" | ||
| }, | ||
| "dependencies": { | ||
| "@plane/utils": "*" | ||
| }, | ||
| "devDependencies": { | ||
| "@plane/eslint-config": "*", | ||
| "@types/node": "^22.5.4", | ||
| "typescript": "^5.3.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import React, { createContext, useEffect } from "react"; | ||
| import { observer } from "mobx-react"; | ||
| import { TranslationStore } from "./store"; | ||
| import { Language, languages } from "../config"; | ||
|
|
||
| // Create the store instance | ||
| const translationStore = new TranslationStore(); | ||
|
|
||
| // Create Context | ||
| export const TranslationContext = createContext<TranslationStore>(translationStore); | ||
|
|
||
| export const TranslationProvider = observer(({ children }: any) => { | ||
| // Handle storage events for cross-tab synchronization | ||
| useEffect(() => { | ||
| const handleStorageChange = (event: StorageEvent) => { | ||
| if (event.key === "userLanguage" && event.newValue) { | ||
| const newLang = event.newValue as Language; | ||
| if (languages.includes(newLang)) { | ||
| translationStore.setLanguage(newLang); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener("storage", handleStorageChange); | ||
| return () => window.removeEventListener("storage", handleStorageChange); | ||
| }, []); | ||
|
|
||
| return <TranslationContext.Provider value={translationStore}>{children}</TranslationContext.Provider>; | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,38 @@ | ||||||||||||||||||||||
| import { makeObservable, observable } from "mobx"; | ||||||||||||||||||||||
| import { Language, fallbackLng, languages, translations } from "../config"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export class TranslationStore { | ||||||||||||||||||||||
| currentLocale: Language = fallbackLng; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| constructor() { | ||||||||||||||||||||||
| makeObservable(this, { | ||||||||||||||||||||||
| currentLocale: observable.ref, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| this.initializeLanguage(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| get availableLanguages() { | ||||||||||||||||||||||
| return languages; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| t(key: string) { | ||||||||||||||||||||||
| return translations[this.currentLocale]?.[key] || translations[fallbackLng][key] || key; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+18
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance translation lookup robustness The current translation lookup could be improved in several ways:
- t(key: string) {
- return translations[this.currentLocale]?.[key] || translations[fallbackLng][key] || key;
+ t(key: string) {
+ const translation = translations[this.currentLocale]?.[key] || translations[fallbackLng][key];
+ if (!translation && process.env.NODE_ENV === 'development') {
+ console.warn(`Missing translation for key: ${key} in locale: ${this.currentLocale}`);
+ }
+ return translation || key;
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| setLanguage(lng: Language) { | ||||||||||||||||||||||
| localStorage.setItem("userLanguage", lng); | ||||||||||||||||||||||
| this.currentLocale = lng; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| initializeLanguage() { | ||||||||||||||||||||||
| if (typeof window === "undefined") return; | ||||||||||||||||||||||
| const savedLocale = localStorage.getItem("userLanguage") as Language; | ||||||||||||||||||||||
| if (savedLocale && languages.includes(savedLocale)) { | ||||||||||||||||||||||
| this.setLanguage(savedLocale); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| const browserLang = navigator.language.split("-")[0] as Language; | ||||||||||||||||||||||
| const newLocale = languages.includes(browserLang as Language) ? (browserLang as Language) : fallbackLng; | ||||||||||||||||||||||
| this.setLanguage(newLocale); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import en from "../locales/en/translations.json"; | ||
| import fr from "../locales/fr/translations.json"; | ||
| import es from "../locales/es/translations.json"; | ||
| import ja from "../locales/ja/translations.json"; | ||
|
|
||
| export type Language = (typeof languages)[number]; | ||
| export type Translations = { | ||
| [key: string]: { | ||
| [key: string]: string; | ||
| }; | ||
| }; | ||
|
|
||
| export const fallbackLng = "en"; | ||
| export const languages = ["en", "fr", "es", "ja"] as const; | ||
| export const translations: Translations = { | ||
| en, | ||
| fr, | ||
| es, | ||
| ja, | ||
| }; | ||
|
|
||
| export const SUPPORTED_LANGUAGES = [ | ||
| { | ||
| label: "English", | ||
| value: "en", | ||
| }, | ||
| { | ||
| label: "French", | ||
| value: "fr", | ||
| }, | ||
| { | ||
| label: "Spanish", | ||
| value: "es", | ||
| }, | ||
| { | ||
| label: "Japanese", | ||
| value: "ja", | ||
| }, | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./use-translation"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { useContext } from "react"; | ||
| import { TranslationContext } from "../components"; | ||
| import { Language } from "../config"; | ||
|
|
||
| export function useTranslation() { | ||
| const store = useContext(TranslationContext); | ||
| if (!store) { | ||
| throw new Error("useTranslation must be used within a TranslationProvider"); | ||
| } | ||
|
|
||
| return { | ||
| t: (key: string) => store.t(key), | ||
| currentLocale: store.currentLocale, | ||
| changeLanguage: (lng: Language) => store.setLanguage(lng), | ||
| languages: store.availableLanguages, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export * from "./config"; | ||
| export * from "./components"; | ||
| export * from "./hooks"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "submit": "Submit", | ||
| "cancel": "Cancel", | ||
| "loading": "Loading", | ||
| "error": "Error", | ||
| "success": "Success", | ||
| "warning": "Warning", | ||
| "info": "Info", | ||
| "close": "Close", | ||
| "yes": "Yes", | ||
| "no": "No", | ||
| "ok": "OK", | ||
| "name": "Name", | ||
| "description": "Description", | ||
| "search": "Search", | ||
| "add_member": "Add member", | ||
| "remove_member": "Remove member", | ||
| "add_members": "Add members", | ||
| "remove_members": "Remove members", | ||
| "add": "Add", | ||
| "remove": "Remove", | ||
| "add_new": "Add new", | ||
| "remove_selected": "Remove selected", | ||
| "first_name": "First name", | ||
| "last_name": "Last name", | ||
| "email": "Email", | ||
| "display_name": "Display name", | ||
| "role": "Role", | ||
| "timezone": "Timezone", | ||
| "avatar": "Avatar", | ||
| "cover_image": "Cover image", | ||
| "password": "Password", | ||
| "change_cover": "Change cover", | ||
| "language": "Language", | ||
| "saving": "Saving...", | ||
| "save_changes": "Save changes", | ||
| "deactivate_account": "Deactivate account", | ||
| "deactivate_account_description": "When deactivating an account, all of the data and resources within that account will be permanently removed and cannot be recovered.", | ||
| "profile_settings": "Profile settings", | ||
| "your_account": "Your account", | ||
| "profile": "Profile", | ||
| "security": "Security", | ||
| "activity": "Activity", | ||
| "appearance": "Appearance", | ||
| "notifications": "Notifications", | ||
| "workspaces": "Workspaces", | ||
| "create_workspace": "Create workspace", | ||
| "invitations": "Invitations" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "submit": "Enviar", | ||
| "cancel": "Cancelar", | ||
| "loading": "Cargando", | ||
| "error": "Error", | ||
| "success": "Éxito", | ||
| "warning": "Advertencia", | ||
| "info": "Información", | ||
| "close": "Cerrar", | ||
| "yes": "Sí", | ||
| "no": "No", | ||
| "ok": "OK", | ||
| "name": "Nombre", | ||
| "description": "Descripción", | ||
| "search": "Buscar", | ||
| "add_member": "Agregar miembro", | ||
| "remove_member": "Eliminar miembro", | ||
| "add_members": "Agregar miembros", | ||
| "remove_members": "Eliminar miembros", | ||
| "add": "Agregar", | ||
| "remove": "Eliminar", | ||
| "add_new": "Agregar nuevo", | ||
| "remove_selected": "Eliminar seleccionados", | ||
| "first_name": "Nombre", | ||
| "last_name": "Apellido", | ||
| "email": "Correo electrónico", | ||
| "display_name": "Nombre para mostrar", | ||
| "role": "Rol", | ||
| "timezone": "Zona horaria", | ||
| "avatar": "Avatar", | ||
| "cover_image": "Imagen de portada", | ||
| "password": "Contraseña", | ||
| "change_cover": "Cambiar portada", | ||
| "language": "Idioma", | ||
| "saving": "Guardando...", | ||
| "save_changes": "Guardar cambios", | ||
| "deactivate_account": "Desactivar cuenta", | ||
| "deactivate_account_description": "Al desactivar una cuenta, todos los datos y recursos dentro de esa cuenta se eliminarán permanentemente y no se podrán recuperar.", | ||
| "profile_settings": "Configuración de perfil", | ||
| "your_account": "Tu cuenta", | ||
| "profile": "Perfil", | ||
| "security": "Seguridad", | ||
| "activity": "Actividad", | ||
| "appearance": "Apariencia", | ||
| "notifications": "Notificaciones", | ||
| "workspaces": "Workspaces", | ||
| "create_workspace": "Crear workspace", | ||
| "invitations": "Invitaciones" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "submit": "Soumettre", | ||
| "cancel": "Annuler", | ||
| "loading": "Chargement", | ||
| "error": "Erreur", | ||
| "success": "Succès", | ||
| "warning": "Avertissement", | ||
| "info": "Info", | ||
| "close": "Fermer", | ||
| "yes": "Oui", | ||
| "no": "Non", | ||
| "ok": "OK", | ||
| "name": "Nom", | ||
| "description": "Description", | ||
| "search": "Rechercher", | ||
| "add_member": "Ajouter un membre", | ||
| "remove_member": "Supprimer un membre", | ||
| "add_members": "Ajouter des membres", | ||
| "remove_members": "Supprimer des membres", | ||
| "add": "Ajouter", | ||
| "remove": "Supprimer", | ||
| "add_new": "Ajouter nouveau", | ||
| "remove_selected": "Supprimer la sélection", | ||
| "first_name": "Prénom", | ||
| "last_name": "Nom de famille", | ||
| "email": "Email", | ||
| "display_name": "Nom d'affichage", | ||
| "role": "Rôle", | ||
| "timezone": "Fuseau horaire", | ||
| "avatar": "Avatar", | ||
| "cover_image": "Image de couverture", | ||
| "password": "Mot de passe", | ||
| "change_cover": "Modifier la couverture", | ||
| "language": "Langue", | ||
| "saving": "Enregistrement...", | ||
| "save_changes": "Enregistrer les modifications", | ||
| "deactivate_account": "Désactiver le compte", | ||
| "deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées.", | ||
| "profile_settings": "Paramètres du profil", | ||
| "your_account": "Votre compte", | ||
| "profile": "Profil", | ||
| "security": " Sécurité", | ||
| "activity": "Activité", | ||
| "appearance": "Apparence", | ||
| "notifications": "Notifications", | ||
| "workspaces": "Workspaces", | ||
| "create_workspace": "Créer un workspace", | ||
| "invitations": "Invitations" | ||
| } | ||
|
Comment on lines
+1
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add missing label-related translations. Given that this PR adds functionality for label management, please add translations for label-related terms such as:
Also, consider these improvements:
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "submit": "送信", | ||
| "cancel": "キャンセル", | ||
| "loading": "読み込み中", | ||
| "error": "エラー", | ||
| "success": "成功", | ||
| "warning": "警告", | ||
| "info": "情報", | ||
| "close": "閉じる", | ||
| "yes": "はい", | ||
| "no": "いいえ", | ||
| "ok": "OK", | ||
| "name": "名前", | ||
| "description": "説明", | ||
| "search": "検索", | ||
| "add_member": "メンバーを追加", | ||
| "remove_member": "メンバーを削除", | ||
| "add_members": "メンバーを追加", | ||
| "remove_members": "メンバーを削除", | ||
| "add": "追加", | ||
| "remove": "削除", | ||
| "add_new": "新規追加", | ||
| "remove_selected": "選択項目を削除", | ||
| "first_name": "名", | ||
| "last_name": "姓", | ||
| "email": "メールアドレス", | ||
| "display_name": "表示名", | ||
| "role": "役割", | ||
| "timezone": "タイムゾーン", | ||
| "avatar": "アバター", | ||
| "cover_image": "カバー画像", | ||
| "password": "パスワード", | ||
| "change_cover": "カバーを変更", | ||
| "language": "言語", | ||
| "saving": "保存中...", | ||
| "save_changes": "変更を保存", | ||
| "deactivate_account": "アカウントを無効化", | ||
| "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。", | ||
| "profile_settings": "プロフィール設定", | ||
| "your_account": "アカウント", | ||
| "profile": "プロフィール", | ||
| "security": "セキュリティ", | ||
| "activity": "アクティビティ", | ||
| "appearance": "アピアンス", | ||
| "notifications": "通知", | ||
| "workspaces": "ワークスペース", | ||
| "create_workspace": "ワークスペースを作成", | ||
| "invitations": "招待" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "extends": "@plane/typescript-config/react-library.json", | ||
| "compilerOptions": { | ||
| "jsx": "react", | ||
| "lib": ["esnext", "dom"], | ||
| "resolveJsonModule": true | ||
| }, | ||
| "include": ["./src"], | ||
| "exclude": ["dist", "build", "node_modules"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add proper typing for children prop
Instead of using
any, consider using proper React types for the children prop.📝 Committable suggestion