feat(client): typed locale standard seat in the slot framework

Registrations declare a dictionary namespace (locale: NS) and the renderer
synthesizes a typed t prop for the entry's component from the installed
LocaleFace; the seat binding is re-derived per locale revision, so a language
switch hands out fresh t references and memoized consumers re-render through
ordinary shallow comparison. LocaleNamespaceMap is the declare-merge table
(namespace -> dictionary key union); TranslateNS<'ns'> is the
namespace-addressed translate type (namespace keys plus the shared common
vocabulary), carried by the t seat and by the locale service's typed bind.

LocaleService implements the face (lookup ns -> common -> zh -> key,
revision-carrying snapshots with subscriber isolation) and installs it
through the boot-once slots.installLocale seam, mirroring the renderer
install. The typed register(ns, {zh, en}) overload checks each dictionary
against the namespace's key union and requires every shipped locale, so a
missing or extra key and an unbalanced translation are compile errors.
Dictionary registration bumps the face revision without emitting
locale/change — the event now means exactly 'the active locale switched',
so registration-heavy boot cannot storm event listeners.
This commit is contained in:
imccyu
2026-07-30 01:04:56 +08:00
parent 1f242753ec
commit c317fbc489
44 changed files with 925 additions and 189 deletions
@@ -32,6 +32,7 @@ export function SidebarRoot({
width,
startSession,
toggleSidebar,
t,
renderSlot,
}: SidebarRootComponentProps) {
// Wide content stays mounted while the collapse animates (fading via
@@ -67,7 +68,7 @@ export function SidebarRoot({
<button
type="button"
className={clsx(css.brand, css.wide)}
aria-label="New session"
aria-label={t('session.new.label')}
onClick={() => { startSession() }}
>
<BrandWordmark />
@@ -75,11 +76,11 @@ export function SidebarRoot({
)}
{/* Rail resting state is the whale mark; hovering swaps in the panel
icon (the expand affordance, figma sidebar-hover flow). */}
<Tooltip label="Open sidebar" disabled={wide}>
<Tooltip label={t('toggle.open')} disabled={wide}>
<button
type="button"
className={clsx(css.iconButton, css.toggle)}
aria-label={collapsed ? 'Open sidebar' : 'Collapse sidebar'}
aria-label={collapsed ? t('toggle.open') : t('toggle.collapse')}
onClick={() => { toggleSidebar() }}
>
{!wide && <FishLogo className={css.railFish} size={24} />}
@@ -89,15 +90,15 @@ export function SidebarRoot({
</Tooltip>
</div>
<Tooltip label="New session" disabled={wide}>
<Tooltip label={t('session.new.label')} disabled={wide}>
<button
type="button"
className={css.newSession}
aria-label="New session"
aria-label={t('session.new.label')}
onClick={() => { startSession() }}
>
<IconNewChatOutline16 size={wide ? 14 : 18} />
{wide && <span className={clsx(css.newSessionLabel, css.wide)}>New Session</span>}
{wide && <span className={clsx(css.newSessionLabel, css.wide)}>{t('session.new')}</span>}
</button>
</Tooltip>
@@ -6,7 +6,7 @@
* `sidebar.workspaces` registrant's (ui-workspace), and the foot is the
* `sidebar.settings` registrant's (ui-settings).
*/
import type { PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import type { PropsLocale, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
// Type-only: pulls ui-layout's SlotMap merge (the 'sidebar' entry) into every
// program that sees this contract, so PropsRuntime<'sidebar'> resolves.
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
@@ -68,7 +68,9 @@ export type SidebarRootInjected = {
/**
* Full component props: layout owner state/actions plus the declared holes'
* render shares and this package's injected callbacks. No store is registered.
* render shares, this package's injected callbacks, and the standard locale
* seat. No store is registered.
*/
export type SidebarRootComponentProps =
PropsRuntime<'sidebar'> & PropsRenderSlots<'sidebar.workspaces' | 'sidebar.settings'> & SidebarRootInjected
PropsRuntime<'sidebar'> & PropsRenderSlots<'sidebar.workspaces' | 'sidebar.settings'>
& SidebarRootInjected & PropsLocale<'sidebar'>
+18 -1
View File
@@ -1,17 +1,33 @@
/** Registers the sidebar shell into the layout-owned slot. */
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
import type {} from '@deepseek-ai/dsh-client-locale/client'
import type { SidebarRootInjected } from './contract/slots.ts'
import { SidebarRoot } from './SidebarRoot.tsx'
import { en, zh, type SidebarKey } from './locales.ts'
export type { SidebarRootComponentProps, SidebarRootInjected, SidebarSectionOwnerProps, SidebarSettingsOwnerProps } from './contract/slots.ts'
export type { SidebarKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
/** Sidebar shell controls copy. */
sidebar: SidebarKey
}
}
/** Dictionary namespace owned by this plugin (shell controls copy). */
const NS = 'sidebar'
/** Services required by the sidebar plugin. */
export const inject = ['slots', 'layout', 'sessions', 'workspaces']
export const inject = ['slots', 'layout', 'sessions', 'workspaces', 'locale']
/** Registers the sidebar shell and its service callbacks.
* @param ctx - Client root context.
*/
export function apply(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries')
const injectProps = (): SidebarRootInjected => ({
// The shell's New Session button rides the runtime's shared action
// (recent-Workspace targeting; explicit Workspace wins for scoped actions).
@@ -21,6 +37,7 @@ export function apply(ctx: ClientContext): void {
ctx.effect(
() => ctx.slots.register({
name: 'sidebar',
locale: NS,
// The shell owns geometry; ui-workspace registers the whole browsing
// region (header, search, session list, workspace dialogs), ui-settings
// registers the foot trigger + settings panel.
@@ -0,0 +1,20 @@
/** `sidebar` namespace dictionaries: shell controls (brand row, New Session, fold toggle). */
/** Simplified Chinese dictionary (the key-set source of truth). */
export const zh = {
'session.new': '新会话',
'session.new.label': '新建会话',
'toggle.open': '打开侧边栏',
'toggle.collapse': '收起侧边栏',
} satisfies Record<string, string>
/** The sidebar namespace key union. */
export type SidebarKey = keyof typeof zh
/** English dictionary, checked complete against the zh key set. */
export const en: Record<SidebarKey, string> = {
'session.new': 'New Session',
'session.new.label': 'New session',
'toggle.open': 'Open sidebar',
'toggle.collapse': 'Collapse sidebar',
}