fix(web): stop spurious page refresh on account menu open; plugin log auto-refresh as switch

Two unrelated frontend fixes:

- LanguageSelector mounts each time the sidebar account dropdown opens and
  unconditionally called i18n.changeLanguage() on mount, emitting a
  languageChanged event even when the language was unchanged. That handed
  every useTranslation() consumer a fresh `t` reference, re-running effects
  keyed on `t` (e.g. the plugins page system-status fetch) and surfacing as
  a page "refresh". Guard the call so it only fires on an actual change.

- Plugin logs auto-refresh control changed from a toggle Button to a
  Switch + Label; the on/off button i18n keys are replaced by a single
  static logsAutoRefresh label across all 8 locales.
This commit is contained in:
RockChinQ
2026-06-21 11:58:01 -04:00
parent 9daf22d661
commit 812b1fff4c
10 changed files with 33 additions and 28 deletions
@@ -3,6 +3,8 @@ import { httpClient } from '@/app/infra/http/HttpClient';
import { useTranslation } from 'react-i18next';
import { PluginLogEntry } from '@/app/infra/entities/plugin';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
@@ -116,17 +118,19 @@ export default function PluginLogs({
/>
{t('plugins.logsRefresh')}
</Button>
<Button
type="button"
variant={autoRefresh ? 'default' : 'outline'}
size="sm"
className="h-8"
onClick={() => setAutoRefresh((v) => !v)}
>
{autoRefresh
? t('plugins.logsAutoRefreshOn')
: t('plugins.logsAutoRefreshOff')}
</Button>
<div className="flex items-center gap-2">
<Switch
id="plugin-logs-auto-refresh"
checked={autoRefresh}
onCheckedChange={setAutoRefresh}
/>
<Label
htmlFor="plugin-logs-auto-refresh"
className="cursor-pointer text-sm font-normal text-muted-foreground"
>
{t('plugins.logsAutoRefresh')}
</Label>
</div>
</div>
<div
+10 -1
View File
@@ -56,7 +56,16 @@ export function LanguageSelector({
const savedLanguage = localStorage.getItem('langbot_language');
if (savedLanguage) {
i18n.changeLanguage(savedLanguage);
// Only switch when the active language actually differs. Calling
// i18n.changeLanguage() unconditionally on every mount emits a
// `languageChanged` event even when nothing changed, which hands every
// useTranslation() consumer a fresh `t` reference and re-runs effects
// that depend on `t` (e.g. data refetches). Since this selector mounts
// each time the account dropdown opens, that surfaced as a spurious
// page "refresh". Guard the call to keep mounts side-effect-free.
if (i18n.language !== savedLanguage) {
i18n.changeLanguage(savedLanguage);
}
setCurrentLanguage(savedLanguage);
} else {
const browserLanguage = navigator.language;
+1 -2
View File
@@ -593,8 +593,7 @@ const enUS = {
tabLogs: 'Logs',
logsLevelAll: 'All levels',
logsRefresh: 'Refresh',
logsAutoRefreshOn: 'Auto-refresh: On',
logsAutoRefreshOff: 'Auto-refresh: Off',
logsAutoRefresh: 'Auto-refresh',
logsEmpty:
'No logs yet. Logs printed by the plugin via logger will appear here.',
fileUpload: {
+1 -2
View File
@@ -605,8 +605,7 @@ const esES = {
tabLogs: 'Registros',
logsLevelAll: 'Todos los niveles',
logsRefresh: 'Actualizar',
logsAutoRefreshOn: 'Auto-actualizar: Activado',
logsAutoRefreshOff: 'Auto-actualizar: Desactivado',
logsAutoRefresh: 'Auto-actualizar',
logsEmpty:
'Aún no hay registros. Los registros que el plugin imprima mediante logger aparecerán aquí.',
fileUpload: {
+1 -2
View File
@@ -598,8 +598,7 @@ const jaJP = {
tabLogs: 'ログ',
logsLevelAll: 'すべてのレベル',
logsRefresh: '更新',
logsAutoRefreshOn: '自動更新:オン',
logsAutoRefreshOff: '自動更新:オフ',
logsAutoRefresh: '自動更新',
logsEmpty:
'ログはまだありません。プラグインが logger で出力したログがここに表示されます。',
fileUpload: {
+1 -2
View File
@@ -604,8 +604,7 @@ const ruRU = {
tabLogs: 'Журналы',
logsLevelAll: 'Все уровни',
logsRefresh: 'Обновить',
logsAutoRefreshOn: 'Автообновление: вкл.',
logsAutoRefreshOff: 'Автообновление: выкл.',
logsAutoRefresh: 'Автообновление',
logsEmpty:
'Журналов пока нет. Здесь появятся логи, выводимые плагином через logger.',
fileUpload: {
+1 -2
View File
@@ -585,8 +585,7 @@ const thTH = {
tabLogs: 'บันทึก',
logsLevelAll: 'ทุกระดับ',
logsRefresh: 'รีเฟรช',
logsAutoRefreshOn: 'รีเฟรชอัตโนมัติ: เปิด',
logsAutoRefreshOff: 'รีเฟรชอัตโนมัติ: ปิด',
logsAutoRefresh: 'รีเฟรชอัตโนมัติ',
logsEmpty: 'ยังไม่มีบันทึก บันทึกที่ปลั๊กอินพิมพ์ผ่าน logger จะแสดงที่นี่',
fileUpload: {
tooLarge: 'ขนาดไฟล์เกินขีดจำกัด 10MB',
+1 -2
View File
@@ -599,8 +599,7 @@ const viVN = {
tabLogs: 'Nhật ký',
logsLevelAll: 'Tất cả cấp độ',
logsRefresh: 'Làm mới',
logsAutoRefreshOn: 'Tự động làm mới: Bật',
logsAutoRefreshOff: 'Tự động làm mới: Tắt',
logsAutoRefresh: 'Tự động làm mới',
logsEmpty:
'Chưa có nhật ký. Nhật ký do plugin in qua logger sẽ hiển thị ở đây.',
fileUpload: {
+1 -2
View File
@@ -566,8 +566,7 @@ const zhHans = {
tabLogs: '日志',
logsLevelAll: '全部级别',
logsRefresh: '刷新',
logsAutoRefreshOn: '自动刷新:开',
logsAutoRefreshOff: '自动刷新:关',
logsAutoRefresh: '自动刷新',
logsEmpty: '暂无日志。插件通过 logger 打印的日志会显示在这里。',
fileUpload: {
tooLarge: '文件大小超过 10MB 限制',
+1 -2
View File
@@ -566,8 +566,7 @@ const zhHant = {
tabLogs: '日誌',
logsLevelAll: '全部級別',
logsRefresh: '重新整理',
logsAutoRefreshOn: '自動重新整理:開',
logsAutoRefreshOff: '自動重新整理:關',
logsAutoRefresh: '自動重新整理',
logsEmpty: '暫無日誌。外掛透過 logger 列印的日誌會顯示在這裡。',
fileUpload: {
tooLarge: '檔案大小超過 10MB 限制',