fix(build): add ScrollTrigger component, fix JSX syntax, dedup i18n

- Add missing ScrollTrigger component for infinite scroll
- Fix JSX placement inside ternary expression
- Remove 290 duplicate i18n keys across 13 translation files
- Fix it.ts duplicate memories.saveError
This commit is contained in:
Maurice
2026-04-13 21:55:59 +02:00
parent 87de60d8de
commit 88e1d075e0
14 changed files with 20 additions and 302 deletions
-1
View File
@@ -1097,7 +1097,6 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'budget.settlement': 'التسوية',
'budget.settlementInfo': 'انقر على صورة العضو في بند الميزانية لتحديده باللون الأخضر — وهذا يعني أنه دفع. ثم تُظهر التسوية من يدين لمن وبكم.',
'budget.netBalances': 'الأرصدة الصافية',
'budget.linkedToReservation': 'مرتبط بحجز — قم بتحرير الاسم هناك',
// Files
'files.title': 'الملفات',
-16
View File
@@ -1066,7 +1066,6 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'budget.settlement': 'Acerto',
'budget.settlementInfo': 'Clique no avatar de um membro em um item do orçamento para marcá-lo em verde — significa que ele pagou. O acerto mostra quem deve quanto a quem.',
'budget.netBalances': 'Saldos líquidos',
'budget.linkedToReservation': 'Vinculado a uma reserva — edite o nome lá',
// Files
'files.title': 'Arquivos',
@@ -1163,9 +1162,6 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'packing.template': 'Modelo',
'packing.templateApplied': '{count} itens adicionados do modelo',
'packing.templateError': 'Falha ao aplicar modelo',
'packing.saveAsTemplate': 'Salvar como modelo',
'packing.templateName': 'Nome do modelo',
'packing.templateSaved': 'Lista de bagagem salva como modelo',
'packing.bags': 'Malas',
'packing.noBag': 'Sem mala',
'packing.totalWeight': 'Peso total',
@@ -1804,21 +1800,9 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'common.justNow': 'agora mesmo',
'common.hoursAgo': 'há {count}h',
'common.daysAgo': 'há {count}d',
'budget.linkedToReservation': 'Vinculado a uma reserva — edite o nome lá',
'packing.saveAsTemplate': 'Salvar como modelo',
'packing.templateName': 'Nome do modelo',
'packing.templateSaved': 'Lista de bagagem salva como modelo',
'memories.notConnectedMultipleHint': 'Conecte qualquer um destes provedores de fotos: {provider_names} em Configurações para poder adicionar fotos a esta viagem.',
'memories.providerUrl': 'URL do servidor',
'memories.providerApiKey': 'Chave da API',
'memories.providerUsername': 'Nome de usuário',
'memories.providerPassword': 'Senha',
'memories.saveError': 'Não foi possível salvar as configurações de {provider_name}',
'memories.saveRouteNotConfigured': 'A rota de salvamento não está configurada para este provedor',
'memories.testRouteNotConfigured': 'A rota de teste não está configurada para este provedor',
'memories.fillRequiredFields': 'Por favor preencha todos os campos obrigatórios',
'memories.selectAlbumMultiple': 'Selecionar álbum',
'memories.selectPhotosMultiple': 'Selecionar fotos',
'journey.title': 'Jornada',
'journey.subtitle': 'Registre suas viagens em tempo real',
'journey.new': 'Nova jornada',
-13
View File
@@ -1095,7 +1095,6 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'budget.settlement': 'Vyúčtování',
'budget.settlementInfo': 'Klikněte na avatar člena u rozpočtové položky pro zelené označení to znamená, že zaplatil. Vyúčtování pak ukazuje, kdo komu a kolik dluží.',
'budget.netBalances': 'Čisté zůstatky',
'budget.linkedToReservation': 'Propojeno s rezervací — upravte název tam',
// Soubory (Files)
'files.title': 'Soubory',
@@ -1806,21 +1805,9 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'common.justNow': 'právě teď',
'common.hoursAgo': 'před {count} h',
'common.daysAgo': 'před {count} d',
'budget.linkedToReservation': 'Propojeno s rezervací — upravte název tam',
'packing.saveAsTemplate': 'Uložit jako šablonu',
'packing.templateName': 'Název šablony',
'packing.templateSaved': 'Balicí seznam uložen jako šablona',
'memories.notConnectedMultipleHint': 'Připojte některého z těchto poskytovatelů fotek: {provider_names} v Nastavení, abyste mohli přidávat fotky k tomuto výletu.',
'memories.providerUrl': 'URL serveru',
'memories.providerApiKey': 'API klíč',
'memories.providerUsername': 'Uživatelské jméno',
'memories.providerPassword': 'Heslo',
'memories.saveError': 'Nepodařilo se uložit nastavení {provider_name}',
'memories.saveRouteNotConfigured': 'Trasa uložení není nakonfigurována pro tohoto poskytovatele',
'memories.testRouteNotConfigured': 'Testovací trasa není nakonfigurována pro tohoto poskytovatele',
'memories.fillRequiredFields': 'Prosím vyplňte všechna povinná pole',
'memories.selectAlbumMultiple': 'Vybrat album',
'memories.selectPhotosMultiple': 'Vybrat fotky',
'journey.title': 'Cestovní deník',
'journey.subtitle': 'Zaznamenávejte své cesty průběžně',
'journey.new': 'Nový cestovní deník',
-8
View File
@@ -2050,14 +2050,6 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'dayplan.mobile.allAssigned': 'Alle Orte zugeordnet',
'dayplan.mobile.noMatch': 'Kein Treffer',
'dayplan.mobile.createNew': 'Neuen Ort erstellen',
'memories.notConnectedMultipleHint': 'Connect any of these photo providers: {provider_names} in Settings to be able add photos to this trip.',
'memories.providerUrl': 'Server URL',
'memories.providerApiKey': 'API Key',
'memories.providerUsername': 'Username',
'memories.providerPassword': 'Password',
'memories.saveError': 'Could not save {provider_name} settings',
'memories.selectAlbumMultiple': 'Select Album',
'memories.selectPhotosMultiple': 'Select Photos',
// OAuth scope groups
'oauth.scope.group.trips': 'Reisen',
-13
View File
@@ -1053,7 +1053,6 @@ const es: Record<string, string> = {
'budget.settlement': 'Liquidación',
'budget.settlementInfo': 'Haz clic en el avatar de un miembro en una partida del presupuesto para marcarlo en verde — esto significa que ha pagado. La liquidación muestra quién debe cuánto a quién.',
'budget.netBalances': 'Saldos netos',
'budget.linkedToReservation': 'Vinculado a una reserva — edita el nombre allí',
// Files
'files.title': 'Archivos',
@@ -1811,18 +1810,6 @@ const es: Record<string, string> = {
'common.justNow': 'justo ahora',
'common.hoursAgo': 'hace {count}h',
'common.daysAgo': 'hace {count}d',
'budget.linkedToReservation': 'Vinculado a una reserva — edita el nombre allí',
'packing.saveAsTemplate': 'Guardar como plantilla',
'packing.templateName': 'Nombre de la plantilla',
'packing.templateSaved': 'Lista de equipaje guardada como plantilla',
'memories.notConnectedMultipleHint': 'Conecta cualquiera de estos proveedores de fotos: {provider_names} en Ajustes para poder añadir fotos a este viaje.',
'memories.providerUrl': 'URL del servidor',
'memories.providerApiKey': 'Clave API',
'memories.providerUsername': 'Nombre de usuario',
'memories.providerPassword': 'Contraseña',
'memories.saveError': 'No se pudo guardar la configuración de {provider_name}',
'memories.selectAlbumMultiple': 'Seleccionar álbum',
'memories.selectPhotosMultiple': 'Seleccionar fotos',
'journey.title': 'Travesía',
'journey.subtitle': 'Registra tus viajes en tiempo real',
'journey.new': 'Nueva travesía',
-13
View File
@@ -1093,7 +1093,6 @@ const fr: Record<string, string> = {
'budget.settlement': 'Règlement',
'budget.settlementInfo': 'Cliquez sur l\'avatar d\'un membre sur un poste budgétaire pour le marquer en vert — cela signifie qu\'il a payé. Le règlement indique ensuite qui doit combien à qui.',
'budget.netBalances': 'Soldes nets',
'budget.linkedToReservation': 'Lié à une réservation — modifiez le nom là-bas',
// Files
'files.title': 'Fichiers',
@@ -1805,18 +1804,6 @@ const fr: Record<string, string> = {
'common.justNow': 'à l\'instant',
'common.hoursAgo': 'il y a {count}h',
'common.daysAgo': 'il y a {count}j',
'budget.linkedToReservation': 'Lié à une réservation — modifiez le nom là-bas',
'packing.saveAsTemplate': 'Enregistrer comme modèle',
'packing.templateName': 'Nom du modèle',
'packing.templateSaved': 'Liste de bagages enregistrée comme modèle',
'memories.notConnectedMultipleHint': 'Connectez l\'un de ces fournisseurs de photos : {provider_names} dans les Paramètres pour pouvoir ajouter des photos à ce voyage.',
'memories.providerUrl': 'URL du serveur',
'memories.providerApiKey': 'Clé API',
'memories.providerUsername': 'Nom d\'utilisateur',
'memories.providerPassword': 'Mot de passe',
'memories.saveError': 'Impossible d\'enregistrer les paramètres de {provider_name}',
'memories.selectAlbumMultiple': 'Sélectionner un album',
'memories.selectPhotosMultiple': 'Sélectionner des photos',
'journey.title': 'Journal de voyage',
'journey.subtitle': 'Suivez vos voyages en temps réel',
'journey.new': 'Nouveau journal',
-13
View File
@@ -1094,7 +1094,6 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'budget.settlement': 'Elszámolás',
'budget.settlementInfo': 'Kattints egy tag avatárjára egy költségvetési tételen a zöld jelöléshez — ez azt jelenti, hogy fizetett. Az elszámolás ezután mutatja, ki kinek mennyivel tartozik.',
'budget.netBalances': 'Nettó egyenlegek',
'budget.linkedToReservation': 'Foglaláshoz kapcsolva — ott módosítsa a nevet',
// Fájlok
'files.title': 'Fájlok',
@@ -1803,21 +1802,9 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'common.justNow': 'az imént',
'common.hoursAgo': '{count} órája',
'common.daysAgo': '{count} napja',
'budget.linkedToReservation': 'Foglaláshoz kapcsolva — a nevet ott módosítsd',
'packing.saveAsTemplate': 'Mentés sablonként',
'packing.templateName': 'Sablon neve',
'packing.templateSaved': 'Csomaglista sablonként mentve',
'memories.notConnectedMultipleHint': 'Csatlakoztasd valamelyik fotószolgáltatót: {provider_names} a Beállításokban, hogy fotókat adhass hozzá ehhez az úthoz.',
'memories.providerUrl': 'Szerver URL',
'memories.providerApiKey': 'API-kulcs',
'memories.providerUsername': 'Felhasználónév',
'memories.providerPassword': 'Jelszó',
'memories.saveError': 'Nem sikerült menteni a(z) {provider_name} beállításait',
'memories.saveRouteNotConfigured': 'A mentési útvonal nincs konfigurálva ehhez a szolgáltatóhoz',
'memories.testRouteNotConfigured': 'A tesztútvonal nincs konfigurálva ehhez a szolgáltatóhoz',
'memories.fillRequiredFields': 'Kérjük töltse ki az összes kötelező mezőt',
'memories.selectAlbumMultiple': 'Album kiválasztása',
'memories.selectPhotosMultiple': 'Fotók kiválasztása',
'journey.title': 'Útinaplók',
'journey.subtitle': 'Kövesse nyomon utazásait valós időben',
'journey.new': 'Új útinapló',
-13
View File
@@ -1094,7 +1094,6 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'budget.settlement': 'Regolamento',
'budget.settlementInfo': 'Clicca sull\'avatar di un membro su una voce di budget per contrassegnarlo in verde — significa che ha pagato. Il regolamento mostra poi chi deve quanto a chi.',
'budget.netBalances': 'Saldi netti',
'budget.linkedToReservation': 'Collegato a una prenotazione — modifica il nome lì',
// Files
'files.title': 'File',
@@ -1806,18 +1805,6 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'common.justNow': 'proprio ora',
'common.hoursAgo': '{count}h fa',
'common.daysAgo': '{count}g fa',
'budget.linkedToReservation': 'Collegato a una prenotazione — modifica il nome lì',
'packing.saveAsTemplate': 'Salva come modello',
'packing.templateName': 'Nome del modello',
'packing.templateSaved': 'Lista bagagli salvata come modello',
'memories.notConnectedMultipleHint': 'Collega uno di questi fornitori di foto: {provider_names} nelle Impostazioni per poter aggiungere foto a questo viaggio.',
'memories.providerUrl': 'URL del server',
'memories.providerApiKey': 'Chiave API',
'memories.providerUsername': 'Nome utente',
'memories.providerPassword': 'Password',
'memories.saveError': 'Impossibile salvare le impostazioni di {provider_name}',
'memories.selectAlbumMultiple': 'Seleziona album',
'memories.selectPhotosMultiple': 'Seleziona foto',
'journey.title': 'Diario di viaggio',
'journey.subtitle': 'Segui i tuoi viaggi in tempo reale',
'journey.new': 'Nuovo diario',
-13
View File
@@ -1093,7 +1093,6 @@ const nl: Record<string, string> = {
'budget.settlement': 'Afrekening',
'budget.settlementInfo': 'Klik op de avatar van een lid bij een budgetpost om deze groen te markeren — dit betekent dat diegene heeft betaald. De afrekening toont vervolgens wie wie hoeveel verschuldigd is.',
'budget.netBalances': 'Nettosaldi',
'budget.linkedToReservation': 'Gekoppeld aan een reservering — bewerk de naam daar',
// Files
'files.title': 'Bestanden',
@@ -1805,18 +1804,6 @@ const nl: Record<string, string> = {
'common.justNow': 'zojuist',
'common.hoursAgo': '{count}u geleden',
'common.daysAgo': '{count}d geleden',
'budget.linkedToReservation': 'Gekoppeld aan een reservering — bewerk de naam daar',
'packing.saveAsTemplate': 'Opslaan als sjabloon',
'packing.templateName': 'Sjabloonnaam',
'packing.templateSaved': 'Paklijst opgeslagen als sjabloon',
'memories.notConnectedMultipleHint': 'Verbind een van deze foto-aanbieders: {provider_names} in Instellingen om foto\'s aan deze reis toe te voegen.',
'memories.providerUrl': 'Server-URL',
'memories.providerApiKey': 'API-sleutel',
'memories.providerUsername': 'Gebruikersnaam',
'memories.providerPassword': 'Wachtwoord',
'memories.saveError': 'Kon {provider_name}-instellingen niet opslaan',
'memories.selectAlbumMultiple': 'Selecteer album',
'memories.selectPhotosMultiple': 'Selecteer foto\'s',
'journey.title': 'Reisverslag',
'journey.subtitle': 'Leg je reizen vast terwijl je onderweg bent',
'journey.new': 'Nieuw reisverslag',
-16
View File
@@ -1148,9 +1148,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'packing.template': 'Szablon',
'packing.templateApplied': '{count} przedmiotów dodanych z szablonu',
'packing.templateError': 'Nie udało się zastosować szablonu',
'packing.saveAsTemplate': 'Zapisz jako szablon',
'packing.templateName': 'Nazwa szablonu',
'packing.templateSaved': 'Lista pakowania zapisana jako szablon',
'packing.bags': 'Torby',
'packing.noBag': 'Nieprzypisane',
'packing.totalWeight': 'Waga całkowita',
@@ -1614,7 +1611,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'inspector.trackStats': 'Statystyki trasy',
'budget.exportCsv': 'Eksportuj CSV',
'budget.table.date': 'Data',
'budget.linkedToReservation': 'Powiązane z rezerwacją — edytuj nazwę tam',
'memories.testFirst': 'Najpierw przetestuj połączenie',
'memories.linkAlbum': 'Połącz album',
'memories.selectAlbum': 'Wybierz album Immich',
@@ -1798,21 +1794,9 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'common.justNow': 'przed chwilą',
'common.hoursAgo': '{count} godz. temu',
'common.daysAgo': '{count} dn. temu',
'budget.linkedToReservation': 'Powiązane z rezerwacją — edytuj nazwę tam',
'packing.saveAsTemplate': 'Zapisz jako szablon',
'packing.templateName': 'Nazwa szablonu',
'packing.templateSaved': 'Lista pakowania zapisana jako szablon',
'memories.notConnectedMultipleHint': 'Połącz jednego z tych dostawców zdjęć: {provider_names} w Ustawieniach, aby dodawać zdjęcia do tej podróży.',
'memories.providerUrl': 'Adres URL serwera',
'memories.providerApiKey': 'Klucz API',
'memories.providerUsername': 'Nazwa użytkownika',
'memories.providerPassword': 'Hasło',
'memories.saveError': 'Nie udało się zapisać ustawień {provider_name}',
'memories.saveRouteNotConfigured': 'Trasa zapisu nie jest skonfigurowana dla tego dostawcy',
'memories.testRouteNotConfigured': 'Trasa testowa nie jest skonfigurowana dla tego dostawcy',
'memories.fillRequiredFields': 'Proszę wypełnić wszystkie wymagane pola',
'memories.selectAlbumMultiple': 'Wybierz album',
'memories.selectPhotosMultiple': 'Wybierz zdjęcia',
'journey.title': 'Dziennik podróży',
'journey.subtitle': 'Dokumentuj swoje podróże na bieżąco',
'journey.new': 'Nowy dziennik podróży',
-13
View File
@@ -1093,7 +1093,6 @@ const ru: Record<string, string> = {
'budget.settlement': 'Взаиморасчёт',
'budget.settlementInfo': 'Нажмите на аватар участника в строке бюджета, чтобы отметить его зелёным — это значит, что он заплатил. Взаиморасчёт покажет, кто кому и сколько должен.',
'budget.netBalances': 'Чистые балансы',
'budget.linkedToReservation': 'Привязано к бронированию — измените название там',
// Files
'files.title': 'Файлы',
@@ -1802,21 +1801,9 @@ const ru: Record<string, string> = {
'common.justNow': 'только что',
'common.hoursAgo': '{count} ч назад',
'common.daysAgo': '{count} д назад',
'budget.linkedToReservation': 'Привязано к бронированию — измените название там',
'packing.saveAsTemplate': 'Сохранить как шаблон',
'packing.templateName': 'Название шаблона',
'packing.templateSaved': 'Список вещей сохранён как шаблон',
'memories.notConnectedMultipleHint': 'Подключите любого из этих фото-провайдеров: {provider_names} в Настройках, чтобы добавлять фото к этой поездке.',
'memories.providerUrl': 'URL сервера',
'memories.providerApiKey': 'API-ключ',
'memories.providerUsername': 'Имя пользователя',
'memories.providerPassword': 'Пароль',
'memories.saveError': 'Не удалось сохранить настройки {provider_name}',
'memories.saveRouteNotConfigured': 'Маршрут сохранения не настроен для этого провайдера',
'memories.testRouteNotConfigured': 'Маршрут тестирования не настроен для этого провайдера',
'memories.fillRequiredFields': 'Пожалуйста, заполните все обязательные поля',
'memories.selectAlbumMultiple': 'Выбрать альбом',
'memories.selectPhotosMultiple': 'Выбрать фото',
'journey.title': 'Путешествие',
'journey.subtitle': 'Отслеживайте свои путешествия в реальном времени',
'journey.new': 'Новое путешествие',
-13
View File
@@ -1093,7 +1093,6 @@ const zh: Record<string, string> = {
'budget.settlement': '结算',
'budget.settlementInfo': '点击预算项目上的成员头像将其标记为绿色——表示该成员已付款。结算会显示谁欠谁多少。',
'budget.netBalances': '净余额',
'budget.linkedToReservation': '已链接到预订——在那里编辑名称',
// Files
'files.title': '文件',
@@ -1802,21 +1801,9 @@ const zh: Record<string, string> = {
'common.justNow': '刚刚',
'common.hoursAgo': '{count}小时前',
'common.daysAgo': '{count}天前',
'budget.linkedToReservation': '已关联预订 — 请在预订中编辑名称',
'packing.saveAsTemplate': '保存为模板',
'packing.templateName': '模板名称',
'packing.templateSaved': '打包清单已保存为模板',
'memories.notConnectedMultipleHint': '在设置中连接以下任一照片服务:{provider_names},以便为此旅行添加照片。',
'memories.providerUrl': '服务器地址',
'memories.providerApiKey': 'API 密钥',
'memories.providerUsername': '用户名',
'memories.providerPassword': '密码',
'memories.saveError': '无法保存 {provider_name} 设置',
'memories.saveRouteNotConfigured': '此提供商未配置保存路由',
'memories.testRouteNotConfigured': '此提供商未配置测试路由',
'memories.fillRequiredFields': '请填写所有必填字段',
'memories.selectAlbumMultiple': '选择相册',
'memories.selectPhotosMultiple': '选择照片',
'journey.title': '旅程',
'journey.subtitle': '实时记录你的旅行',
'journey.new': '新建旅程',
-146
View File
@@ -133,8 +133,6 @@ const zhTw: Record<string, string> = {
'dashboard.coverRemoveError': '移除失敗',
'dashboard.titleRequired': '標題為必填項',
'dashboard.endDateError': '結束日期必須晚於開始日期',
'dashboard.dayCount': '天數',
'dashboard.dayCountHint': '未設定旅行日期時的規劃天數。',
// Settings
'settings.title': '設定',
@@ -1763,21 +1761,9 @@ const zhTw: Record<string, string> = {
'common.justNow': '剛剛',
'common.hoursAgo': '{count}小時前',
'common.daysAgo': '{count}天前',
'budget.linkedToReservation': '已關聯預訂 — 請在預訂中編輯名稱',
'packing.saveAsTemplate': '儲存為範本',
'packing.templateName': '範本名稱',
'packing.templateSaved': '打包清單已儲存為範本',
'memories.notConnectedMultipleHint': '在設定中連接以下任一照片服務:{provider_names},以便為此旅行新增照片。',
'memories.providerUrl': '伺服器位址',
'memories.providerApiKey': 'API 金鑰',
'memories.providerUsername': '使用者名稱',
'memories.providerPassword': '密碼',
'memories.saveError': '無法儲存 {provider_name} 設定',
'memories.saveRouteNotConfigured': '此提供商未設定儲存路由',
'memories.testRouteNotConfigured': '此提供商未設定測試路由',
'memories.fillRequiredFields': '請填寫所有必填欄位',
'memories.selectAlbumMultiple': '選擇相簿',
'memories.selectPhotosMultiple': '選擇照片',
'journey.title': '旅程',
'journey.subtitle': '即時記錄你的旅行',
'journey.new': '新建旅程',
@@ -2021,112 +2007,9 @@ const zhTw: Record<string, string> = {
'dayplan.mobile.createNew': '建立新地點',
'admin.addons.catalog.journey.name': '旅程',
'admin.addons.catalog.journey.description': '旅行追蹤與旅行日誌,包含打卡、照片和每日故事',
'dashboard.dayCount': '天數',
'dashboard.dayCountHint': '未設定旅行日期時規劃的天數。',
'settings.tabs.display': '顯示',
'settings.tabs.map': '地圖',
'settings.tabs.notifications': '通知',
'settings.tabs.integrations': '整合',
'settings.tabs.account': '帳戶',
'settings.tabs.about': '關於',
'settings.notifyVersionAvailable': '有新版本可用',
'settings.notificationPreferences.email': '電子郵件',
'settings.notificationPreferences.webhook': 'Webhook',
'settings.notificationPreferences.inapp': '應用內',
'settings.notificationPreferences.noChannels': '尚未設定通知管道。請聯繫管理員設定電子郵件或 Webhook 通知。',
'settings.webhookUrl.label': 'Webhook 網址',
'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
'settings.webhookUrl.hint': '輸入你的 Discord、Slack 或自訂 Webhook 網址以接收通知。',
'settings.webhookUrl.save': '儲存',
'settings.webhookUrl.saved': 'Webhook 網址已儲存',
'settings.webhookUrl.test': '測試',
'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功',
'settings.webhookUrl.testFailed': '測試 Webhook 失敗',
'admin.notifications.emailPanel.title': '電子郵件 (SMTP)',
'admin.notifications.webhookPanel.title': 'Webhook',
'admin.notifications.inappPanel.title': '應用內',
'admin.notifications.inappPanel.hint': '應用內通知始終處於啟用狀態,無法全域停用。',
'admin.notifications.adminWebhookPanel.title': '管理員 Webhook',
'admin.notifications.adminWebhookPanel.hint': '此 Webhook 僅用於管理員通知(例如版本更新提醒)。它與每位使用者的 Webhook 分開,設定後將始終觸發。',
'admin.notifications.adminWebhookPanel.saved': '管理員 Webhook 網址已儲存',
'admin.notifications.adminWebhookPanel.testSuccess': '測試 Webhook 傳送成功',
'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 失敗',
'admin.notifications.adminWebhookPanel.alwaysOnHint': '設定網址後管理員 Webhook 將始終觸發',
'admin.notifications.adminNotificationsHint': '設定哪些管道傳送僅限管理員的通知(例如版本更新提醒)。',
'settings.about.reportBug': '回報錯誤',
'settings.about.reportBugHint': '發現問題?請告訴我們',
'settings.about.featureRequest': '功能建議',
'settings.about.featureRequestHint': '提出新功能建議',
'settings.about.wikiHint': '文件與指南',
'settings.about.description': 'TREK 是一個自架式旅行規劃工具,幫助你從第一個想法到最後一個回憶來組織旅行。日程規劃、預算、打包清單、照片等等——全部集中在一處,在你自己的伺服器上。',
'settings.about.madeWith': '以',
'settings.about.madeBy': '由 Maurice 和不斷壯大的開源社群製作。',
'admin.tabs.notifications': '通知',
'atlas.confirmUnmarkRegion': '將此地區從已造訪清單中移除?',
'atlas.markRegionVisitedHint': '將此地區新增至已造訪清單',
'trip.tabs.lists': '清單',
'trip.tabs.listsShort': '清單',
'reservations.price': '價格',
'reservations.budgetCategory': '預算類別',
'reservations.budgetCategoryPlaceholder': '例如 交通、住宿',
'reservations.budgetCategoryAuto': '自動(依預訂類型)',
'reservations.budgetHint': '儲存時將自動建立一筆預算項目。',
'reservations.departureDate': '出發日期',
'reservations.arrivalDate': '抵達日期',
'reservations.departureTime': '出發時間',
'reservations.arrivalTime': '抵達時間',
'reservations.pickupDate': '取車日期',
'reservations.returnDate': '還車日期',
'reservations.pickupTime': '取車時間',
'reservations.returnTime': '還車時間',
'reservations.endDate': '結束日期',
'reservations.meta.departureTimezone': '出發時區',
'reservations.meta.arrivalTimezone': '抵達時區',
'reservations.span.departure': '出發',
'reservations.span.arrival': '抵達',
'reservations.span.inTransit': '運輸中',
'reservations.span.pickup': '取車',
'reservations.span.return': '還車',
'reservations.span.active': '使用中',
'reservations.span.start': '開始',
'reservations.span.end': '結束',
'reservations.span.ongoing': '進行中',
'reservations.validation.endBeforeStart': '結束日期/時間必須晚於開始日期/時間',
'notifications.versionAvailable.title': '有可用更新',
'notifications.versionAvailable.text': 'TREK {version} 現已推出。',
'notifications.versionAvailable.button': '查看詳情',
'todo.subtab.packing': '打包清單',
'todo.subtab.todo': '待辦事項',
'todo.completed': '已完成',
'todo.filter.all': '全部',
'todo.filter.open': '未完成',
'todo.filter.done': '已完成',
'todo.uncategorized': '未分類',
'todo.namePlaceholder': '任務名稱',
'todo.descriptionPlaceholder': '描述(可選)',
'todo.unassigned': '未指派',
'todo.noCategory': '無類別',
'todo.hasDescription': '有描述',
'todo.addItem': '新增任務...',
'todo.newCategory': '類別名稱',
'todo.addCategory': '新增類別',
'todo.newItem': '新任務',
'todo.empty': '還沒有任務。新增一個任務開始吧!',
'todo.filter.my': '我的任務',
'todo.filter.overdue': '已逾期',
'todo.sidebar.tasks': '任務',
'todo.sidebar.categories': '類別',
'todo.detail.title': '任務',
'todo.detail.description': '描述',
'todo.detail.category': '類別',
'todo.detail.dueDate': '截止日期',
'todo.detail.assignedTo': '指派給',
'todo.detail.delete': '刪除',
'todo.detail.save': '儲存變更',
'todo.sortByPrio': '優先順序',
'todo.detail.priority': '優先順序',
'todo.detail.noPriority': '無',
'todo.detail.create': '建立任務',
'notif.test.title': '[測試] 通知',
'notif.test.simple.text': '這是一則簡單的測試通知。',
'notif.test.boolean.text': '你是否接受這則測試通知?',
@@ -2153,39 +2036,10 @@ const zhTw: Record<string, string> = {
'notif.action.view_photos': '查看照片',
'notif.action.view_vacay': '查看 Vacay',
'notif.action.view_admin': '前往管理',
'notifications.versionAvailable.title': '有可用更新',
'notifications.versionAvailable.text': 'TREK {version} 現已推出。',
'notifications.versionAvailable.button': '查看詳情',
// Notifications — dev test events
'notif.test.title': '[測試] 通知',
'notif.test.simple.text': '這是一條簡單的測試通知。',
'notif.test.boolean.text': '您接受此測試通知嗎?',
'notif.test.navigate.text': '點選下方前往儀表板。',
// Notifications
'notif.trip_invite.title': '行程邀請',
'notif.trip_invite.text': '{actor} 邀請您加入 {trip}',
'notif.booking_change.title': '預訂已更新',
'notif.booking_change.text': '{actor} 已更新 {trip} 中的預訂',
'notif.trip_reminder.title': '行程提醒',
'notif.trip_reminder.text': '您的行程 {trip} 即將開始!',
'notif.vacay_invite.title': 'Vacay 合併邀請',
'notif.vacay_invite.text': '{actor} 邀請您合併假期計畫',
'notif.photos_shared.title': '已分享照片',
'notif.photos_shared.text': '{actor} 在 {trip} 中分享了 {count} 張照片',
'notif.collab_message.title': '新訊息',
'notif.collab_message.text': '{actor} 在 {trip} 中傳送了訊息',
'notif.packing_tagged.title': '行李指派',
'notif.packing_tagged.text': '{actor} 在 {trip} 中將您指派至 {category}',
'notif.version_available.title': '有新版本可用',
'notif.version_available.text': 'TREK {version} 現已推出',
'notif.action.view_trip': '查看行程',
'notif.action.view_collab': '查看訊息',
'notif.action.view_packing': '查看行李',
'notif.action.view_photos': '查看照片',
'notif.action.view_vacay': '查看 Vacay',
'notif.action.view_admin': '前往管理員',
'notif.action.view': '查看',
'notif.action.accept': '接受',
'notif.action.decline': '拒絕',
+20 -11
View File
@@ -1352,6 +1352,24 @@ function WeatherChip({ weather }: { weather: string }) {
)
}
// ── Scroll Trigger ───────────────────────────────────────────────────────
function ScrollTrigger({ onVisible, loading }: { onVisible: () => void; loading: boolean }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting && !loading) onVisible() }, { rootMargin: '200px' })
obs.observe(el)
return () => obs.disconnect()
}, [onVisible, loading])
return (
<div ref={ref} className="flex justify-center py-4 mt-2">
<div className="w-5 h-5 border-2 border-zinc-300 border-t-zinc-900 dark:border-zinc-600 dark:border-t-white rounded-full animate-spin" />
</div>
)
}
// ── Provider Picker ───────────────────────────────────────────────────────
function ProviderPicker({ provider, userId, entries, trips, existingAssetIds, onClose, onAdd }: {
@@ -1698,18 +1716,9 @@ function ProviderPicker({ provider, userId, entries, trips, existingAssetIds, on
</div>
)
})}
{/* Infinite scroll trigger */}
{hasMore && <ScrollTrigger onVisible={loadMorePhotos} loading={loadingMore} />}
</div>
{/* Infinite scroll trigger */}
{hasMore && (
<div className="flex justify-center py-4 mt-2" ref={el => {
if (!el) return
const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) loadMorePhotos() }, { rootMargin: '200px' })
obs.observe(el)
return () => obs.disconnect()
}}>
<div className="w-5 h-5 border-2 border-zinc-300 border-t-zinc-900 dark:border-zinc-600 dark:border-t-white rounded-full animate-spin" />
</div>
)}
)}
</div>