diff --git a/src/workspaces/officerdev/src/apps/AppStore/AppStoreDetail.tsx b/src/workspaces/officerdev/src/apps/AppStore/AppStoreDetail.tsx index 3c8f7b34..c09d7d74 100644 --- a/src/workspaces/officerdev/src/apps/AppStore/AppStoreDetail.tsx +++ b/src/workspaces/officerdev/src/apps/AppStore/AppStoreDetail.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router'; import { Loader2, ExternalLink } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useAppStore, type ConfigField, type InstallMode, type StoreItem } from './useAppStore'; +import { useTranslation } from '../../locales'; // One entry: what it is, how to install it, and what to do about it once installed. // @@ -41,6 +42,7 @@ function Field({ field, value, onChange }: { field: ConfigField; value: string; } export const AppStoreDetail = () => { + const { t } = useTranslation(); const [params] = useSearchParams(); const id = params.get('selected'); const { items, install, setEnabled, uninstall } = useAppStore(); @@ -51,7 +53,11 @@ export const AppStoreDetail = () => { const [log, setLog] = useState([]); if (!item) { - return
Select an app
; + return ( +
+ {t('appStore.selectApp')} +
+ ); } // `existing` is first in `modes` by catalogue rule, so the default lands on "I already have one" — @@ -82,13 +88,13 @@ export const AppStoreDetail = () => { person. `completeAt` links to the page that mints the token. */} {item.install.status === 'blocked' && item.install.lastError && (
-
Needs you
+
{t('appStore.needsYou')}

{item.install.lastError}

)} {item.install.status === 'failed' && item.install.lastError && (
-
Install failed
+
{t('appStore.installFailed')}

{item.install.lastError}

)} @@ -97,7 +103,7 @@ export const AppStoreDetail = () => {
Installed {item.install.mode ? `(${item.install.mode})` : ''} ·{' '} - {item.install.enabled ? 'enabled' : 'disabled'} + {item.install.enabled ? t('appStore.enabled') : t('appStore.disabled')} {item.processStatus && ` · process ${item.processStatus}`}
@@ -106,17 +112,14 @@ export const AppStoreDetail = () => { disabled={setEnabled.isPending} onClick={() => setEnabled.mutate({ id: item.id, enabled: !item.install.enabled })} > - {item.install.enabled ? 'Disable' : 'Enable'} + {item.install.enabled ? t('appStore.disable') : t('appStore.enable')}
{/* Said plainly, because it is the question anyone hesitates over before clicking. */} -

- Disabling stops the sidecar and its container. Uninstalling also removes the containers — your data, - configuration and this app’s tables are kept either way. -

+

{t('appStore.removalNote')}

) : ( // A real
, so pressing Enter in a field installs — the div it used to be silently ate it. diff --git a/src/workspaces/officerdev/src/apps/AppStore/AppStoreList.tsx b/src/workspaces/officerdev/src/apps/AppStore/AppStoreList.tsx index e2830ce7..d77a8dae 100644 --- a/src/workspaces/officerdev/src/apps/AppStore/AppStoreList.tsx +++ b/src/workspaces/officerdev/src/apps/AppStore/AppStoreList.tsx @@ -1,6 +1,7 @@ import { Link, useSearchParams } from 'react-router'; import { Loader2, Check, CircleSlash, AlertTriangle, PauseCircle } from 'lucide-react'; import { useAppStore, type StoreItem } from './useAppStore'; +import { useTranslation } from '../../locales'; // The list of everything installable, with what has happened to each. // @@ -10,26 +11,31 @@ import { useAppStore, type StoreItem } from './useAppStore'; /** One glyph for the whole state, because "installed but the process died" has to be visible at a glance. */ function StatusBadge({ item }: { item: StoreItem }) { + const { t } = useTranslation(); const { status, enabled } = item.install; if (status === 'not-installed') return null; if (status === 'installing') { - return ; + return ; } - if (status === 'failed') return ; + if (status === 'failed') + return ; // Blocked is not a failure: everything worked and it is waiting for something only a person can give. - if (status === 'blocked') return ; - if (!enabled) return ; + if (status === 'blocked') + return ; + if (!enabled) + return ; // Installed and enabled, but the process is not running — the case worth surfacing rather than // showing a tick that lies. if (item.processStatus && item.processStatus !== 'online') { return ; } - return ; + return ; } export const AppStoreList = () => { + const { t } = useTranslation(); const { items, isLoading, error } = useAppStore(); const [params] = useSearchParams(); const selected = params.get('selected'); @@ -37,14 +43,14 @@ export const AppStoreList = () => { if (isLoading) { return (
- Loading + {t('appStore.loading')}
); } if (error) { // Distinguished from "nothing installable", which would otherwise look identical and is not a // problem the user can act on. - return
Could not load the app store.
; + return
{t('appStore.loadFailed')}
; } const installed = items.filter((i) => i.install.status !== 'not-installed'); @@ -66,13 +72,17 @@ export const AppStoreList = () => {
{installed.length > 0 && ( <> -
Installed
+
+ {t('appStore.installed')} +
{installed.map((item) => ( ))} )} -
Available
+
+ {t('appStore.available')} +
{available.map((item) => ( ))} diff --git a/src/workspaces/officerdev/src/locales/en-GB.json b/src/workspaces/officerdev/src/locales/en-GB.json index f6f06cec..68c68567 100644 --- a/src/workspaces/officerdev/src/locales/en-GB.json +++ b/src/workspaces/officerdev/src/locales/en-GB.json @@ -32,5 +32,24 @@ "noShells": "No shells running.", "refresh": "Refresh", "kill": "Kill this shell" + }, + "appStore": { + "loading": "Loading", + "loadFailed": "Could not load the app store.", + "installed": "Installed", + "available": "Available", + "needsYou": "Needs you", + "needsYouShort": "needs you", + "selectApp": "Select an app", + "installFailed": "Install failed", + "uninstall": "Uninstall", + "enable": "Enable", + "disable": "Disable", + "enabled": "enabled", + "disabled": "disabled", + "removalNote": "Disabling stops the sidecar and its container. Uninstalling also removes the containers — your data, configuration and this app’s tables are kept either way.", + "installing": "installing", + "failed": "failed", + "installedIcon": "installed" } } diff --git a/src/workspaces/officerdev/src/locales/pt-PT.json b/src/workspaces/officerdev/src/locales/pt-PT.json index 0a1afc89..4401277e 100644 --- a/src/workspaces/officerdev/src/locales/pt-PT.json +++ b/src/workspaces/officerdev/src/locales/pt-PT.json @@ -32,5 +32,24 @@ "noShells": "Nenhuma shell em execução.", "refresh": "Atualizar", "kill": "Terminar esta shell" + }, + "appStore": { + "loading": "A carregar", + "loadFailed": "Não foi possível carregar a loja de aplicações.", + "installed": "Instaladas", + "available": "Disponíveis", + "needsYou": "Precisa de si", + "needsYouShort": "precisa de si", + "selectApp": "Selecione uma aplicação", + "installFailed": "A instalação falhou", + "uninstall": "Desinstalar", + "enable": "Ativar", + "disable": "Desativar", + "enabled": "ativada", + "disabled": "desativada", + "removalNote": "Desativar para o sidecar e o seu contentor. Desinstalar remove também os contentores — os seus dados, a configuração e as tabelas desta aplicação são mantidos em qualquer dos casos.", + "installing": "a instalar", + "failed": "falhou", + "installedIcon": "instalada" } }