- {plugin.publisher}
- {plugin.version}
- {plugin.platform}
-
- /api{plugin.prefix}
-
-
-
-
-
- {plugin.permissions.length ? plugin.permissions.map((p) => p.key).join(', ') : 'none'}
-
- {/* Shown because installing this runs `git clone` against it. An owner agreeing to put code on
+
+ {plugin.publisher}
+ {plugin.version}
+ {plugin.platform}
+
+ /api{plugin.prefix}
+
+
+
+
+
+ {plugin.permissions.length ? plugin.permissions.map((p) => p.key).join(', ') : 'none'}
+
+ {/* Shown because installing this runs `git clone` against it. An owner agreeing to put code on
their machine should be able to read where it comes from without opening a config file. */}
-
- {plugin.source}
-
+
+ {plugin.source}
+
+
+
+
+
+
+
+
+ {t('plugins.clonesInto')} plugins/{plugin.appName}, then installs it.
+
+
+
+
+
+
-
-
-
-
-
-
- Clones the repository into plugins/{plugin.appName}, then installs it.
-
-
-
-
-
-
-
-);
+ );
+};
/**
* What the marketplace says about a plugin, in the pane where somebody decides whether to install it.
@@ -317,6 +322,7 @@ const DeletePlugin = ({
busy: boolean;
onDelete: (dropData: boolean) => void;
}) => {
+ const { t, tUi } = useTranslation();
const [open, setOpen] = useState(false);
const [dropData, setDropData] = useState(false);
@@ -334,7 +340,7 @@ const DeletePlugin = ({
setOpen(true);
}}
>
- Delete
+ {t('common.delete')}
-
+ /api{plugin.prefix}
- {plugin.publisher}
-
+ {plugin.publisher}
+
{plugin.version}
{plugin.outdated ? (
on disk — installed {plugin.installedVersion}
) : null}
- {plugin.platform}
-
+ {plugin.platform}
+
{plugin.has.sidecar ? (
-
+
{/* Enabled but not online is the state worth naming: the plugin is switched on and its
process is not running, which is broken rather than off. */}
{
) : null}
-
+
{plugin.permissions.length
? plugin.permissions.map((p) => `${p.key}${p.ownerOnly ? ' (owner only)' : ''}`).join(', ')
: 'none — reachable by anyone who can reach the platform'}
@@ -494,7 +501,7 @@ export const PluginDetail = () => {
disabled={busy || (!plugin.dependencies.satisfied && !plugin.dependencies.canInstall)}
onClick={() => run('install', plugin.appName)}
>
- Install
+ {t('plugins.install')}
) : null}
{!plugin.installed ? (
@@ -508,11 +515,11 @@ export const PluginDetail = () => {
<>
{plugin.enabled ? (
) : (
)}
{/* Always offered, not gated on `outdated`. That flag compares the manifest's version to the
@@ -521,7 +528,7 @@ export const PluginDetail = () => {
which cannot fetch: the directory exists, so the clone is skipped and only the apply
steps re-ran. `update` pulls first. */}
{/* Absent while enabled — disable first. `DeletePlugin` decides that itself rather than being
conditioned here, so the rule lives next to the confirmation that explains it. */}
diff --git a/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx b/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx
index 373a8355..ecc995b5 100644
--- a/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx
+++ b/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx
@@ -2,6 +2,7 @@ import { Link, useSearchParams } from 'react-router';
import { Puzzle, AlertTriangle, Database } from 'lucide-react';
import { usePlugins, type OrphanTable, type PluginItem } from './usePlugins';
import { resolveIcon } from '../../utils/resolve-icon';
+import { useTranslation } from '../../locales';
// The left panel: every plugin in the tree, installed or not.
//
@@ -17,11 +18,12 @@ const Status = ({ plugin }: { plugin: PluginItem }) => {
};
export const PluginsList = () => {
+ const { t } = useTranslation();
const { plugins, available, broken, orphans, isLoading, updateAll, updatingAll, updateAllResult } = usePlugins();
const [params] = useSearchParams();
const selected = params.get('selected');
- if (isLoading) return
Loading…
;
+ if (isLoading) return
{t('plugins.loading')}
;
return (
@@ -34,11 +36,13 @@ export const PluginsList = () => {
that: a count would imply a check that has not happened. */}
{plugins.length ? (
- {/* "On this machine", not "Installed" — the list is every plugin DIRECTORY, and an uninstalled
+ {/* t('plugins.onThisMachine'), not "Installed" — the list is every plugin DIRECTORY, and an uninstalled
one stays in it. Under the old heading such a row read "Installed · not installed", which is
how a real uninstall got mistaken for a failed one: the plugin never moved, so nothing on
screen said the directory was still there deciding what a reinstall would run. */}
- On this machine
+
+ {t('plugins.onThisMachine')}
+