From a12b6bfeca9ec8d149f6424800a097dec7430f61 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 20 Nov 2025 19:23:27 +0800 Subject: [PATCH] feat: enable native language emoji search with CLDR data format (#11381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add i18n support and local data to emoji picker - Add emoji-picker-element-data package for offline-first emoji data - Implement i18n translations for emoji picker UI (de, en, es, fr, ja, pt, ru, zh) - Switch from CDN to local emoji data to improve performance and reliability - Add locale mapping to match app language with emoji picker data - Move emoji-picker-element import to EmojiPicker component for better encapsulation - Use proper TypeScript types instead of 'any' for type safety This improves user experience by providing localized emoji picker interface and eliminating dependency on external CDN, ensuring the picker works offline. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: enable native language emoji search with CLDR data format Switch from emojibase to CLDR format for emoji-picker-element data to support full multi-language search functionality. Users can now search for emojis in their native language (e.g., German users can search "Herz" for ❤️, Spanish users can search "corazón"). Also improves type safety by using the LanguageVarious type for locale mappings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- package.json | 1 + .../src/components/EmojiPicker/index.tsx | 99 +++++++++++++++++-- .../components/AddAssistantPresetPopup.tsx | 2 - yarn.lock | 8 ++ 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 36fe62337c..ceb0cbf3ac 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch", "@paymoapp/electron-shutdown-handler": "^1.1.2", "@strongtz/win32-arm64-msvc": "^0.4.7", + "emoji-picker-element-data": "^1", "express": "^5.1.0", "font-list": "^2.0.0", "graceful-fs": "^4.2.11", diff --git a/src/renderer/src/components/EmojiPicker/index.tsx b/src/renderer/src/components/EmojiPicker/index.tsx index 8ba9d3e967..9a4158d469 100644 --- a/src/renderer/src/components/EmojiPicker/index.tsx +++ b/src/renderer/src/components/EmojiPicker/index.tsx @@ -1,35 +1,120 @@ +import 'emoji-picker-element' + import TwemojiCountryFlagsWoff2 from '@renderer/assets/fonts/country-flag-fonts/TwemojiCountryFlags.woff2?url' import { useTheme } from '@renderer/context/ThemeProvider' +import type { LanguageVarious } from '@renderer/types' import { polyfillCountryFlagEmojis } from 'country-flag-emoji-polyfill' +// i18n translations from emoji-picker-element +import de from 'emoji-picker-element/i18n/de' +import en from 'emoji-picker-element/i18n/en' +import es from 'emoji-picker-element/i18n/es' +import fr from 'emoji-picker-element/i18n/fr' +import ja from 'emoji-picker-element/i18n/ja' +import pt_PT from 'emoji-picker-element/i18n/pt_PT' +import ru_RU from 'emoji-picker-element/i18n/ru_RU' +import zh_CN from 'emoji-picker-element/i18n/zh_CN' +import type Picker from 'emoji-picker-element/picker' +import type { EmojiClickEvent, NativeEmoji } from 'emoji-picker-element/shared' +// Emoji data from emoji-picker-element-data (local, no CDN) +// Using CLDR format for full multi-language search support (28 languages) +import dataDE from 'emoji-picker-element-data/de/cldr/data.json?url' +import dataEN from 'emoji-picker-element-data/en/cldr/data.json?url' +import dataES from 'emoji-picker-element-data/es/cldr/data.json?url' +import dataFR from 'emoji-picker-element-data/fr/cldr/data.json?url' +import dataJA from 'emoji-picker-element-data/ja/cldr/data.json?url' +import dataPT from 'emoji-picker-element-data/pt/cldr/data.json?url' +import dataRU from 'emoji-picker-element-data/ru/cldr/data.json?url' +import dataZH from 'emoji-picker-element-data/zh/cldr/data.json?url' +import dataZH_HANT from 'emoji-picker-element-data/zh-hant/cldr/data.json?url' import type { FC } from 'react' import { useEffect, useRef } from 'react' +import { useTranslation } from 'react-i18next' interface Props { onEmojiClick: (emoji: string) => void } +// Mapping from app locale to emoji-picker-element i18n +const i18nMap: Record = { + 'en-US': en, + 'zh-CN': zh_CN, + 'zh-TW': zh_CN, // Closest available + 'de-DE': de, + 'el-GR': en, // No Greek available, fallback to English + 'es-ES': es, + 'fr-FR': fr, + 'ja-JP': ja, + 'pt-PT': pt_PT, + 'ru-RU': ru_RU +} + +// Mapping from app locale to emoji data URL +// Using CLDR format provides native language search support for all locales +const dataSourceMap: Record = { + 'en-US': dataEN, + 'zh-CN': dataZH, + 'zh-TW': dataZH_HANT, + 'de-DE': dataDE, + 'el-GR': dataEN, // No Greek CLDR available, fallback to English + 'es-ES': dataES, + 'fr-FR': dataFR, + 'ja-JP': dataJA, + 'pt-PT': dataPT, + 'ru-RU': dataRU +} + +// Mapping from app locale to emoji-picker-element locale string +// Must match the data source locale for proper IndexedDB caching +const localeMap: Record = { + 'en-US': 'en', + 'zh-CN': 'zh', + 'zh-TW': 'zh-hant', + 'de-DE': 'de', + 'el-GR': 'en', + 'es-ES': 'es', + 'fr-FR': 'fr', + 'ja-JP': 'ja', + 'pt-PT': 'pt', + 'ru-RU': 'ru' +} + const EmojiPicker: FC = ({ onEmojiClick }) => { const { theme } = useTheme() - const ref = useRef(null) + const { i18n } = useTranslation() + const ref = useRef(null) + const currentLocale = i18n.language as LanguageVarious useEffect(() => { polyfillCountryFlagEmojis('Twemoji Mozilla', TwemojiCountryFlagsWoff2) }, []) + // Configure picker with i18n and dataSource useEffect(() => { - const refValue = ref.current + const picker = ref.current + if (picker) { + picker.i18n = i18nMap[currentLocale] || en + picker.dataSource = dataSourceMap[currentLocale] || dataEN + picker.locale = localeMap[currentLocale] || 'en' + } + }, [currentLocale]) - if (refValue) { - const handleEmojiClick = (event: any) => { + useEffect(() => { + const picker = ref.current + + if (picker) { + const handleEmojiClick = (event: EmojiClickEvent) => { event.stopPropagation() - onEmojiClick(event.detail.unicode || event.detail.emoji.unicode) + const { detail } = event + // Use detail.unicode (processed with skin tone) or fallback to emoji's unicode for native emoji + const unicode = detail.unicode || ('unicode' in detail.emoji ? (detail.emoji as NativeEmoji).unicode : '') + onEmojiClick(unicode) } // 添加事件监听器 - refValue.addEventListener('emoji-click', handleEmojiClick) + picker.addEventListener('emoji-click', handleEmojiClick) // 清理事件监听器 return () => { - refValue.removeEventListener('emoji-click', handleEmojiClick) + picker.removeEventListener('emoji-click', handleEmojiClick) } } return diff --git a/src/renderer/src/pages/store/assistants/presets/components/AddAssistantPresetPopup.tsx b/src/renderer/src/pages/store/assistants/presets/components/AddAssistantPresetPopup.tsx index d5558b8e88..a6f733dfef 100644 --- a/src/renderer/src/pages/store/assistants/presets/components/AddAssistantPresetPopup.tsx +++ b/src/renderer/src/pages/store/assistants/presets/components/AddAssistantPresetPopup.tsx @@ -1,5 +1,3 @@ -import 'emoji-picker-element' - import { CheckOutlined, LoadingOutlined, RollbackOutlined, ThunderboltOutlined } from '@ant-design/icons' import { loggerService } from '@logger' import EmojiPicker from '@renderer/components/EmojiPicker' diff --git a/yarn.lock b/yarn.lock index 18bde2062e..240532dfb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10074,6 +10074,7 @@ __metadata: electron-window-state: "npm:^5.0.3" emittery: "npm:^1.0.3" emoji-picker-element: "npm:^1.22.1" + emoji-picker-element-data: "npm:^1" epub: "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch" eslint: "npm:^9.22.0" eslint-plugin-import-zod: "npm:^1.2.0" @@ -13655,6 +13656,13 @@ __metadata: languageName: node linkType: hard +"emoji-picker-element-data@npm:^1": + version: 1.8.0 + resolution: "emoji-picker-element-data@npm:1.8.0" + checksum: 10c0/c8976b636205a0cc90d2690859a1193add71a948dadf743962b47c338a4c3715768404d0ccbc02156608b44abf41f3e1d51756e06f1bbed9d164dd4cb1752103 + languageName: node + linkType: hard + "emoji-picker-element@npm:^1.22.1": version: 1.26.3 resolution: "emoji-picker-element@npm:1.26.3"