activity, capabilities, chat history and the locale picker (21 -> 2)

The locale picker's labels are now AUTONYMS computed from Intl -- each language named
in its own language, so "Português europeu" reads as Portuguese whichever locale the
UI is in. That is the convention for a language picker and the useful behaviour:
somebody who cannot read the current language still has to find theirs.

CapabilityPage interpolated the capability KIND into six strings by lowercasing it in
code. "skill" does not lowercase into a Portuguese noun, so kind is a parameter now.

What is left is two strings, both deliberate: see the next commit message.
This commit is contained in:
2026-08-23 05:37:04 +00:00
parent 0b85d42197
commit bde59eb74e
8 changed files with 120 additions and 39 deletions
@@ -12,6 +12,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } f
import { useClient } from 'hooks/useClient';
import { Card } from '@/components/Card';
import { useChat, EmbeddableChat } from 'officerdev';
import { useTranslation } from '@/lib/i18n';
type CapabilitySummary = {
dirName: string;
name: string;
@@ -301,6 +302,7 @@ export const CapabilityChat = ({
};
export const FrontmatterBlock = ({ yaml }: { yaml: string }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
return (
@@ -310,7 +312,7 @@ export const FrontmatterBlock = ({ yaml }: { yaml: string }) => {
className="flex w-full items-center gap-1.5 px-3 py-1.5 text-duck-dark/50 hover:text-duck-dark/70 cursor-pointer transition-colors"
>
<ChevronRight className={`h-3.5 w-3.5 transition-transform ${open ? 'rotate-90' : ''}`} />
<span className="text-xs font-medium">Frontmatter</span>
<span className="text-xs font-medium">{t('capability.frontmatter')}</span>
</button>
{open && (
<pre className="px-4 pb-3 text-xs text-duck-dark/60 whitespace-pre-wrap font-mono leading-relaxed">{yaml}</pre>
@@ -330,6 +332,7 @@ export const CapabilityList = ({
showCreate,
onShowCreateChange,
}: CapabilityListProps) => {
const { t, tUi } = useTranslation();
const client = useClient();
const qc = useQueryClient();
const [internalCreating, setInternalCreating] = useState(false);
@@ -356,7 +359,7 @@ export const CapabilityList = ({
setNewName('');
onCreate(res.dirName);
} catch {
toast.error(`Failed to create ${kind}`);
toast.error(t('capability.createFailed', { kind: kind.toLowerCase() }));
}
};
@@ -427,7 +430,7 @@ export const CapabilityList = ({
<input
value={internalSearch}
onChange={(ev) => setInternalSearch(ev.target.value)}
placeholder={`Search ${kind.toLowerCase()}s...`}
placeholder={t('capability.searchPlaceholder', { kind: `${kind.toLowerCase()}s` })}
className="w-full rounded border border-duck-dark/15 bg-background/80 pl-7 pr-2 py-1 text-base md:text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
@@ -452,7 +455,7 @@ export const CapabilityList = ({
<p className="text-sm text-duck-dark/40 px-4 py-6 text-center">No {kind.toLowerCase()}s found</p>
)}
{items.length > 0 && filtered.length === 0 && (
<p className="text-sm text-duck-dark/40 px-4 py-6 text-center">No matches</p>
<p className="text-sm text-duck-dark/40 px-4 py-6 text-center">{t('capability.noMatches')}</p>
)}
</div>
</>
@@ -469,6 +472,7 @@ export const CapabilityList = ({
* them for free — no reset effect racing the navigation. `edit` also makes the editor pane linkable.
*/
export const CapabilityPage = ({ kind, endpoint, queryKey, basePath }: CapabilityPageProps) => {
const { t, tUi } = useTranslation();
const client = useClient();
const qc = useQueryClient();
const navigate = useNavigate();
@@ -497,7 +501,7 @@ export const CapabilityPage = ({ kind, endpoint, queryKey, basePath }: Capabilit
navigate(basePath, { replace: true });
await qc.invalidateQueries({ queryKey: [queryKey] });
} catch {
toast.error(`Failed to delete ${kind}`);
toast.error(t('capability.deleteFailed', { kind: kind.toLowerCase() }));
}
};
@@ -526,7 +530,7 @@ export const CapabilityPage = ({ kind, endpoint, queryKey, basePath }: Capabilit
<ArrowLeft className="h-4 w-4 text-duck-dark/60" />
</Link>
<span className="text-sm font-medium text-duck-dark/70 flex-1">
{detail?.name ?? `Select a ${kind.toLowerCase()}`}
{detail?.name ?? t('capability.selectOne', { kind: kind.toLowerCase() })}
</span>
{detail && (
<>
@@ -554,10 +558,10 @@ export const CapabilityPage = ({ kind, endpoint, queryKey, basePath }: Capabilit
</ReactMarkdown>
</article>
) : detail ? (
<p className="text-sm text-duck-dark/40 text-center mt-12">Empty file</p>
<p className="text-sm text-duck-dark/40 text-center mt-12">{t('capability.emptyFile')}</p>
) : (
<p className="text-sm text-duck-dark/40 text-center mt-12">
Select a {kind.toLowerCase()} to view its contents
{t('capability.selectToView', { kind: kind.toLowerCase() })}
</p>
)}
</div>
@@ -606,10 +610,10 @@ export const CapabilityPage = ({ kind, endpoint, queryKey, basePath }: Capabilit
</DialogHeader>
<div className="flex justify-end gap-2 mt-2">
<Button variant="outline" onClick={() => setDeleteConfirm(false)} className="cursor-pointer">
Cancel
{tUi('button.cancel')}
</Button>
<Button variant="destructive" onClick={handleDelete} className="cursor-pointer">
Delete
{t('capability.delete')}
</Button>
</div>
</DialogContent>