mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 01:36:38 +08:00
docs(web): consolidate maintainer guidance (#40758)
This commit is contained in:
@@ -15,6 +15,7 @@ This package owns shared design tokens, CSS-first Tailwind styles, the `cn()` ut
|
||||
|
||||
Use the README sections as the detailed owners:
|
||||
|
||||
- [Button and icon-button contracts]
|
||||
- [Imports and public boundaries]
|
||||
- [Typed value contracts]
|
||||
- [Search and picker selection]
|
||||
@@ -22,6 +23,7 @@ Use the README sections as the detailed owners:
|
||||
- [Overlay and portal contracts]
|
||||
- [Development and test boundaries]
|
||||
|
||||
[Button and icon-button contracts]: README.md#button-loading-and-disabled-contract
|
||||
[Development and test boundaries]: README.md#development
|
||||
[Imports and public boundaries]: README.md#imports
|
||||
[Overlay and portal contracts]: README.md#overlay--portal-contract
|
||||
|
||||
+17
-13
@@ -68,7 +68,7 @@ Keep implementation-only render helpers, context values, styling helpers, and up
|
||||
| Actions | `./button`, `./icon-button`, `./toggle` | Visible-label actions, icon-only commands, and persistent toggles. |
|
||||
| Controls | `./segmented-control` | SegmentedControl for mode, filter, and view selection. |
|
||||
| Display | `./collapsible`, `./kbd` | Collapsible disclosure primitive; keyboard input and shortcut keycap primitives. |
|
||||
| Feedback | `./meter`, `./toast` | Meter is inline status; Toast owns the `z-60` layer. |
|
||||
| Feedback | `./meter`, `./progress`, `./status-dot`, `./toast` | Inline and asynchronous status primitives; Toast owns the `z-60` layer. |
|
||||
| Form | `./form`, `./field`, `./fieldset`, `./input`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. |
|
||||
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
|
||||
| Media | `./avatar` | Avatar root, image, and fallback primitives. |
|
||||
@@ -96,7 +96,7 @@ When `loading` is true, `Button` defaults `focusableWhenDisabled` to true. Loadi
|
||||
|
||||
Use `IconButton` for a command represented by one icon and no visible text. Use `Button` when the control has a visible label, including buttons with leading or trailing icons.
|
||||
|
||||
Pass exactly one CSS or React SVG icon and provide either `aria-label` or `aria-labelledby`:
|
||||
Pass exactly one React element containing the decorative glyph and provide either `aria-label` or `aria-labelledby`. Mark the glyph or its decorative wrapper `aria-hidden="true"`; `IconButton` does not add that attribute to its child:
|
||||
|
||||
```tsx
|
||||
<IconButton aria-label="Close">
|
||||
@@ -104,9 +104,11 @@ Pass exactly one CSS or React SVG icon and provide either `aria-label` or `aria-
|
||||
</IconButton>
|
||||
```
|
||||
|
||||
Every icon button must have an `aria-label` or `aria-labelledby`; a tooltip is only a visual enhancement. The child chooses the glyph and its optical size. Omit `variant` for the neutral action-button appearance, or use the same appearance variants as `Button`. Use `tone="destructive"` for destructive intent. Size, radius, colors, hover, disabled, and focus-visible styles belong to `IconButton`; limit `className` to external layout.
|
||||
Every icon button must have an `aria-label` or `aria-labelledby`; a tooltip is only a visual enhancement. The child chooses the glyph and its optical size. Omit `variant` for the IconButton-specific neutral appearance; the other appearance names align with `Button`. Use `tone="destructive"` for destructive intent.
|
||||
|
||||
`IconButton` preserves Base UI Button's `render`, `nativeButton`, event, and ref composition.
|
||||
Size, radius, colors, hover, disabled, and focus-visible styles belong to `IconButton`. Use `className` for external layout or selectors driven by the composed primitive or business-state owner; do not recreate an existing appearance variant.
|
||||
|
||||
When Toggle, Menu, Popover, Tooltip, or Collapsible owns the interaction state, keep that primitive outside and use its `render` prop to render `IconButton` as the final button. `IconButton` preserves Base UI Button's `render`, `nativeButton`, event, and ref composition.
|
||||
|
||||
## Segmented control contract
|
||||
|
||||
@@ -245,19 +247,19 @@ Equivalent: any root element with `isolation: isolate` in CSS. Without it, overl
|
||||
|
||||
Every overlay primitive uses a single, shared z-index. Do **not** override it at call sites.
|
||||
|
||||
| Layer | z-index | Where |
|
||||
| ------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------- |
|
||||
| Overlays (Dialog, AlertDialog, Autocomplete, Combobox, Drawer, Popover, DropdownMenu, ContextMenu, Select, Tooltip) | `z-50` | Positioner / Backdrop |
|
||||
| Toast viewport | `z-60` | One layer above overlays so notifications are never hidden under a dialog. |
|
||||
| Layer | z-index | Where |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------- |
|
||||
| Overlays (Dialog, AlertDialog, Autocomplete, Combobox, Drawer, Popover, PreviewCard, DropdownMenu, ContextMenu, Select, Tooltip) | `z-50` | Positioner / Backdrop |
|
||||
| Toast viewport | `z-60` | One layer above overlays so notifications are never hidden under a dialog. |
|
||||
|
||||
Rationale: Dify UI owns the normal application overlay layer. Overlay primitives share `z-50` and **rely on DOM order** for stacking — the portal mounted later wins. Toast owns `z-60` so notifications remain visible above dialogs, popovers, and other portalled surfaces without falling back to `z-9999`.
|
||||
|
||||
See `[web/docs/overlay.md](../../web/docs/overlay.md)` for the web app overlay best practices.
|
||||
See the [web overlay guide] for the web app overlay best practices.
|
||||
|
||||
### Rules
|
||||
|
||||
- Never add ad hoc `z-*` overrides on primitives from this package. If something is getting clipped, fix the parent overlay structure instead of raising the child primitive.
|
||||
- Never create an extra manual portal on top of our primitives — use the exported content / portal parts such as `DialogContent`, `PopoverContent`, and `DrawerPortal`. Base UI handles focus management, scroll-locking, and dismissal.
|
||||
- Never create an extra manual portal on top of our primitives. Use the exported content or portal parts such as `DialogContent`, `PopoverContent`, and `DrawerPortal`, and preserve each primitive's own focus, modal, and dismissal contract.
|
||||
- When a primitive needs additional presentation chrome (e.g. a custom backdrop), add it **inside** the exported component, not at call sites.
|
||||
|
||||
### Tooltip, preview card, infotip, and popover semantics
|
||||
@@ -270,11 +272,11 @@ See `[web/docs/overlay.md](../../web/docs/overlay.md)` for the web app overlay b
|
||||
|
||||
## Development
|
||||
|
||||
- `vp check packages/dify-ui` (from the repository root) — formatting, lint, and TypeScript diagnostics for the package.
|
||||
- `vp check packages/dify-ui` (from the repository root) — formatting and lint for the package plus the repository-wide TypeScript diagnostics configured by Vite+.
|
||||
- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives.
|
||||
- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`.
|
||||
- `pnpm -C packages/dify-ui test:storybook` — Storybook component tests in Vitest browser mode. Stories without `play` are render and a11y smoke tests; stories with `play` should cover public UI contracts such as opening overlays, keyboard navigation, disabled/loading guards, form submission, and controlled state updates.
|
||||
- `pnpm -C packages/dify-ui type-check` — `tsgo --noEmit` for this package only.
|
||||
- `pnpm -C packages/dify-ui type-check` — TypeScript 7 native type checking for this package only.
|
||||
|
||||
### Test Boundary
|
||||
|
||||
@@ -306,7 +308,7 @@ Set the Base UI test flag in a Vitest setup file to skip those waits:
|
||||
|
||||
`packages/dify-ui/vitest.setup.ts` already applies this for primitive tests.
|
||||
|
||||
See `[AGENTS.md](./AGENTS.md)` for:
|
||||
See [component authoring rules] for:
|
||||
|
||||
- Component authoring rules (one-component-per-folder, `cva` + `cn`, relative imports inside the package, subpath imports from consumers).
|
||||
|
||||
@@ -323,3 +325,5 @@ See `[AGENTS.md](./AGENTS.md)` for:
|
||||
[Base UI forms handbook]: https://base-ui.com/react/handbook/forms
|
||||
[Base UI]: https://base-ui.com/react
|
||||
[Overlay & portal contract]: #overlay--portal-contract
|
||||
[component authoring rules]: ./AGENTS.md
|
||||
[web overlay guide]: ../../web/docs/overlay.md
|
||||
|
||||
@@ -21,6 +21,8 @@ NEXT_PUBLIC_SOCKET_URL=ws://localhost:5001
|
||||
# Dev proxy routes are configured in web/dev-proxy.config.ts.
|
||||
# pnpm -C web run dev:proxy loads web/.env.local before evaluating that config file.
|
||||
DEV_PROXY_TARGET=https://cloud.dify.ai
|
||||
# Defaults to DEV_PROXY_TARGET when omitted. Set this when enterprise API routes use a different origin.
|
||||
DEV_PROXY_ENTERPRISE_TARGET=
|
||||
# Defaults to DEV_PROXY_TARGET when omitted. Set this when Web App public APIs use a different origin.
|
||||
DEV_PROXY_PUBLIC_TARGET=https://udify.app
|
||||
DEV_PROXY_HOST=127.0.0.1
|
||||
|
||||
+4
-2
@@ -9,8 +9,8 @@
|
||||
- User-facing strings must use `web/i18n/en-US/` keys. When adding or renaming a key, update every supported locale with the correct localized value.
|
||||
- For new backend calls and migrated surfaces, use generated `consoleQuery` / `consoleClient` APIs from `@/service/client`. Do not add handwritten REST helpers or DTO mirrors, mock-backed app state, or direct edits to generated contracts.
|
||||
- Prefer `@langgenius/dify-ui/*` primitives, data attributes, and design tokens. Preserve a visible focus indicator on the final focusable element.
|
||||
- Use `Button` for actions with visible text and `IconButton` for icon-only actions. Every `IconButton` needs an `aria-label` or `aria-labelledby`; compose Menu, Popover, Toggle, and Collapsible through `render` so those primitives keep ownership of their state.
|
||||
- Follow `docs/overlay.md` for overlay selection and migration. Migrate a legacy overlay only when the current behavior change actually involves that overlay boundary.
|
||||
- Use `Button` for actions with visible text and `IconButton` for icon-only actions. Every `IconButton` needs an `aria-label` or `aria-labelledby`, and its decorative glyph must be `aria-hidden`. Compose Menu, Popover, Toggle, and Collapsible through `render` so those primitives keep ownership of their state. See the canonical [IconButton contract].
|
||||
- Follow `docs/overlay.md` for overlay selection. Do not introduce a generic web wrapper that recreates Dify UI portal, backdrop, positioning, or z-index behavior.
|
||||
- For custom SVG icons, follow `../packages/iconify-collections/README.md`; do not add generated React icons under `app/components/base/icons/src/`.
|
||||
- `docs/test.md` is the single source of truth for frontend automated-test policy. Skills may route and execute that policy but must not redefine it.
|
||||
|
||||
@@ -23,3 +23,5 @@ This version has breaking changes — APIs, conventions, and file structure may
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
|
||||
[IconButton contract]: ../packages/dify-ui/README.md#icon-button-contract
|
||||
|
||||
+7
-25
@@ -1,29 +1,16 @@
|
||||
# Dify Frontend
|
||||
|
||||
This is a [Next.js] project, but you can dev with [vinext].
|
||||
This is a [Next.js] application with [vinext] as the default local development server.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Run by source code
|
||||
|
||||
Before starting the web frontend service, please make sure the following environment is ready.
|
||||
The required Node.js and pnpm versions are pinned by the repository root `.nvmrc` and `packageManager` field. [Vite+] is also available for repository checks and tests; use its official documentation as the installation reference.
|
||||
|
||||
- [Node.js]
|
||||
- [pnpm]
|
||||
|
||||
You can also use [Vite+] with the corresponding `vp` commands.
|
||||
For example, use `vp install` instead of `pnpm install` and `vp test` instead of `pnpm run test`.
|
||||
|
||||
> [!TIP]
|
||||
> It is recommended to install and enable Corepack to manage package manager versions automatically:
|
||||
>
|
||||
> ```bash
|
||||
> npm install -g corepack
|
||||
> corepack enable
|
||||
> ```
|
||||
>
|
||||
> Learn more: [Corepack]
|
||||
|
||||
Run the following commands from the repository root.
|
||||
|
||||
First, install the dependencies:
|
||||
@@ -34,7 +21,7 @@ pnpm install
|
||||
|
||||
> [!NOTE]
|
||||
> JavaScript dependencies are managed by the workspace files at the repository root: `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`, and `.nvmrc`.
|
||||
> Install dependencies from the repository root, then run frontend scripts from `web/`.
|
||||
> Install dependencies and run the commands below from the repository root.
|
||||
|
||||
Then, configure the environment variables.
|
||||
Create `web/.env.local` and copy the contents from `web/.env.example`.
|
||||
@@ -49,18 +36,14 @@ cp web/.env.example web/.env.local
|
||||
> 1. When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. The frontend and backend must be under the same top-level domain in order to share authentication cookies.
|
||||
> 1. It's necessary to set NEXT_PUBLIC_API_PREFIX and NEXT_PUBLIC_PUBLIC_API_PREFIX to the correct backend API URL.
|
||||
|
||||
Finally, run the development server:
|
||||
Finally, start the default development stack from the repository root. This runs vinext and the local API proxy together:
|
||||
|
||||
```bash
|
||||
pnpm -C web run dev
|
||||
# or if you are using vinext which provides a better development experience
|
||||
pnpm -C web run dev:vinext
|
||||
# (optional) start the dev proxy server so that you can use online API in development
|
||||
# edit web/dev-proxy.config.ts to choose proxy paths
|
||||
# edit web/.env.local to override DEV_PROXY_TARGET, DEV_PROXY_ENTERPRISE_TARGET, DEV_PROXY_HOST, or DEV_PROXY_PORT
|
||||
pnpm -C web run dev:proxy
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Use `pnpm -C web dev` only when you specifically need the Next.js development server without the default vinext process. Proxy environment variables are documented in `web/.env.example`; route ownership remains in `web/dev-proxy.config.ts`.
|
||||
|
||||
Open <http://localhost:3000> with your browser to see the result.
|
||||
|
||||
You can start editing the files under `web/app`.
|
||||
@@ -140,7 +123,6 @@ Visit <https://docs.dify.ai> to view the full documentation.
|
||||
|
||||
The Dify community can be found on [Discord community], where you can ask questions, voice ideas, and share your projects.
|
||||
|
||||
[Corepack]: https://github.com/nodejs/corepack#readme
|
||||
[Discord community]: https://discord.gg/5AEfbxcd9k
|
||||
[Lint Documentation]: ./docs/lint.md
|
||||
[Next.js]: https://nextjs.org
|
||||
|
||||
@@ -480,7 +480,7 @@ function DetailPanel({ appDetail, detail, onClose, onFeedback }: IDetailPanel) {
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiCloseLine className="size-4 text-text-tertiary" />
|
||||
<RiCloseLine aria-hidden className="size-4 text-text-tertiary" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{/* Panel Body */}
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
# Analytics Consent
|
||||
|
||||
Owns CookieYes consent state and the Dify Cloud analytics boundary.
|
||||
This module owns CookieYes consent state and the Dify Cloud analytics boundary.
|
||||
|
||||
## Internal Modules
|
||||
- `request-boundary.ts` decides whether a request is eligible for Cloud analytics before client code mounts.
|
||||
- `cookieyes-consent-bridge.tsx` is the only CookieYes-to-application state adapter.
|
||||
- `consent-store.ts` owns the client consent snapshot; analytics consumers must gate SDK activation or event emission on that snapshot.
|
||||
- `cloud-analytics.tsx` gates analytics scripts and runtime mounting by deployment, environment, host, and route.
|
||||
- `cloud-analytics-runtime.tsx` mounts the consent bridge and client analytics consumers.
|
||||
|
||||
- `consent-store`
|
||||
- `cookieyes-consent-bridge`
|
||||
- `cloud-analytics`
|
||||
- `cloud-analytics-runtime`
|
||||
- `request-boundary`
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/base/amplitude`
|
||||
- `app/components/base/ga`
|
||||
- `app/components/external-attribution-recorder`
|
||||
- `config`
|
||||
- `context/query-client-server`
|
||||
- `features/system-features`
|
||||
Amplitude, Google Analytics, attribution, and other consumers must depend on this boundary instead of reading CookieYes state directly.
|
||||
|
||||
@@ -27,11 +27,11 @@ export default function ThemeSelector() {
|
||||
const getCurrentIcon = () => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
return <span className="i-ri-sun-line size-4 text-text-tertiary" />
|
||||
return <span aria-hidden className="i-ri-sun-line size-4 text-text-tertiary" />
|
||||
case 'dark':
|
||||
return <span className="i-ri-moon-line size-4 text-text-tertiary" />
|
||||
return <span aria-hidden className="i-ri-moon-line size-4 text-text-tertiary" />
|
||||
default:
|
||||
return <span className="i-ri-computer-line size-4 text-text-tertiary" />
|
||||
return <span aria-hidden className="i-ri-computer-line size-4 text-text-tertiary" />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
Global command palette that coordinates detached dialog triggers, typed search, commands, and navigation.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- `actions`: Search actions, command registration, result conversion, and recent-item persistence.
|
||||
- `components`: Goto Anything empty and footer presentation.
|
||||
- `dialog-handle`: The detached Base UI Dialog handle shared by triggers and the lazy dialog root.
|
||||
|
||||
## State Ownership
|
||||
|
||||
- The detached Dialog handle owns open state and trigger focus restoration.
|
||||
@@ -16,18 +10,4 @@ Global command palette that coordinates detached dialog triggers, typed search,
|
||||
- Autocomplete owns option registration, highlighting, keyboard navigation, and item activation.
|
||||
- ScrollArea Viewport is the only results scroll container; Autocomplete List remains the listbox.
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/app`: Application result icons and current application state used by workflow commands.
|
||||
- `app/components/base`: Shared application, dataset, and form types and icons.
|
||||
- `app/components/plugins`: Plugin installation permissions and installer UI.
|
||||
- `app/components/rag-pipeline/goto-anything-search`: RAG pipeline node search owned by the RAG feature.
|
||||
- `app/components/workflow/goto-anything-search`: Workflow node search owned by the workflow feature.
|
||||
- `app/components/workflow/utils/node-navigation`: Workflow node navigation.
|
||||
- `app/components/workflow/workflow-generator`: Workflow generator commands and state.
|
||||
- `app/components/workflow/types`: Workflow node contracts.
|
||||
- `config`: Feature preview configuration.
|
||||
- `context/i18n`: Locale and documentation URL configuration.
|
||||
- `i18n-config`: Client locale updates and supported languages.
|
||||
- `types/app`: Application mode and icon contracts.
|
||||
- `utils/app-redirection`: Application result navigation paths.
|
||||
Search actions adapt application, knowledge, plugin, workflow, and RAG owners into palette results. They must not duplicate those features' authorization, navigation, or query contracts.
|
||||
|
||||
@@ -179,7 +179,7 @@ export default function AccountSetting({
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onCancelAction}
|
||||
>
|
||||
<span className="i-ri-close-line size-5" />
|
||||
<span aria-hidden className="i-ri-close-line size-5" />
|
||||
</IconButton>
|
||||
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
# Member Invitations
|
||||
|
||||
Owns the workspace member invitation form, including email recipient composition, role selection, submission errors, and invitation request state.
|
||||
This module owns the workspace member invitation form: recipient composition, role selection, field and form errors, request state, and the successful invitation result.
|
||||
|
||||
Base UI Form owns field registration, validation, external field errors, and invalid-field focus. TanStack Query owns invitation mutation state. This module keeps only the controlled email composition value and business submission outcome.
|
||||
Base UI Form owns registration, validation, external field errors, and invalid-field focus. TanStack Query owns feature and role queries, the invitation mutation, and cache invalidation. Seat availability comes from `consoleQuery.features.get`; the feature does not mirror it in local or provider state.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
None.
|
||||
|
||||
## External Modules
|
||||
|
||||
| Module | Why this module uses it |
|
||||
| -------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `@dify/contracts/api/console/workspaces` | Types invite responses and documented invite error codes. |
|
||||
| `context/i18n` | Provides the current locale for role queries and invitations. |
|
||||
| `context/provider-context` | Provides workspace seat limits and refresh capability. |
|
||||
| `models/access-control` | Types workspace role options. |
|
||||
| `service/access-control/use-workspace-roles` | Loads paginated role options. |
|
||||
| `service/client` | Executes the invitation mutation. |
|
||||
| `service/use-common` | Invalidates member list queries after a successful invite. |
|
||||
`InviteForm` owns the email draft, parsed recipients, and current submission error. Colocated presentation components may own their transient interaction state; dialog visibility remains caller-owned.
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function IntegrationsSettingModal({
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onCancel}
|
||||
>
|
||||
<span className="i-ri-close-line h-5 w-5" />
|
||||
<span aria-hidden className="i-ri-close-line h-5 w-5" />
|
||||
</IconButton>
|
||||
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,4 @@ After a keyboard user deletes a tool, this module restores focus after the contr
|
||||
|
||||
Tools are identified by `provider_name` and `tool_name`, matching the module's deduplication contract. Callers must replace `value` after `onChange` so the list and pending focus target can settle together.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- `index.tsx`: List state composition, collapse control, and post-delete focus ownership.
|
||||
- `__tests__/focus-restoration.spec.tsx`: Integration coverage through the real tool row and trigger chain.
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/plugins/plugin-detail-panel/tool-selector`: Single-tool trigger, Popover, and configuration form.
|
||||
- `app/components/workflow/nodes/_base/components/mcp-tool-availability`: MCP availability policy.
|
||||
- `service/use-tools`: Installed MCP tool data used by the enabled count.
|
||||
MCP availability remains owned by the workflow policy, and installed-tool data remains owned by its query. This list only adapts those results into ordering, counts, and selection updates.
|
||||
|
||||
@@ -6,18 +6,4 @@ The built-in trigger branch accepts `triggerRef`, which always resolves to its f
|
||||
|
||||
Deletion only reports intent through `onDelete`. This module does not infer sibling order or choose a post-delete focus target; a list composition owner must coordinate that behavior.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- `components/tool-item`: Configured tool row and its primary and secondary actions.
|
||||
- `components/tool-trigger`: Default unconfigured tool trigger.
|
||||
- `components/tool-base-form`, `components/tool-authorization-section`, and `components/tool-settings-panel`: Popover content sections.
|
||||
- `hooks/use-tool-selector` and `hooks/use-plugin-installed-check`: Selector state and installed-provider resolution.
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/plugins/plugin-auth`: Tool authorization controls.
|
||||
- `app/components/plugins/readme-panel`: Plugin documentation entry.
|
||||
- `app/components/tools`: Tool provider contracts and form conversion.
|
||||
- `app/components/workflow/block-selector`: Tool picker and selected-tool contracts.
|
||||
- `app/components/workflow/nodes/_base/components`: MCP availability and plugin recovery actions.
|
||||
- `service/use-plugins` and `service/use-tools`: Installed plugin and tool-provider queries.
|
||||
The Popover owns the selector surface. Nested reasoning and schema configuration use feature-owned forms and Dify UI Dialog rather than introducing another overlay wrapper. Plugin authorization, provider data, and MCP availability remain owned by their source features and queries.
|
||||
|
||||
@@ -93,7 +93,7 @@ export function SubmitRequestDropdown({ dividerAfterFirst }: SubmitRequestDropdo
|
||||
size="lg"
|
||||
className="data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||
>
|
||||
<span className="i-ri-book-open-line size-4 shrink-0" />
|
||||
<span aria-hidden className="i-ri-book-open-line size-4 shrink-0" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ function TaskStatusIndicator({
|
||||
data-error={showErrorStyle ? '' : undefined}
|
||||
className={cn(styles.indicator, className)}
|
||||
>
|
||||
<span className="contents">
|
||||
<span aria-hidden className="contents">
|
||||
<DownloadingIcon active={hasActiveInstall} />
|
||||
{showBadge && (
|
||||
<span className="absolute -top-1.5 -right-1.5 box-content flex size-3.5 items-center justify-center rounded-full border border-components-panel-bg bg-components-panel-bg">
|
||||
|
||||
@@ -4,8 +4,12 @@ This directory owns the cross-route Step-by-step Tour capability.
|
||||
|
||||
- `state.ts` owns the server query graph, the in-memory tour session, and domain commands.
|
||||
- `storage.ts` owns only the persisted shell preference.
|
||||
- `target-registry.ts` owns guide target registration and lookup across route content.
|
||||
- `mount.tsx` composes the checklist, coachmarks, navigation, and analytics.
|
||||
- `coachmark.tsx` owns the feature-specific portal, spotlight geometry, pointer blockers, and target interaction policy.
|
||||
- Route consumers resolve only the guide branch and targets that depend on their page data.
|
||||
|
||||
Server state remains canonical in the TanStack Query cache. Components consume narrow derived atoms
|
||||
and write-only commands instead of a combined account-state facade.
|
||||
|
||||
The checklist uses Dify UI Popover. The coachmark is a deliberate feature-owned exception because ordinary overlay primitives do not model an arbitrary-page spotlight; it is not a reusable overlay primitive.
|
||||
|
||||
@@ -305,7 +305,7 @@ const ProviderDetail = ({ collection, onHide, onRefreshData }: Props) => {
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={onHide}
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<RiCloseLine aria-hidden className="size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Public contract
|
||||
|
||||
`index.tsx` is the only public component entry. It exports `BlockSelectorProps` and defaults to `BlockSelector`. There is no forwarding entry file or compatibility alias.
|
||||
`index.tsx` is the canonical entry for the main `BlockSelector` component and its `BlockSelectorProps`. Specialized selectors and shared contracts remain owned by their existing colocated modules; do not add forwarding entry files or compatibility aliases.
|
||||
|
||||
The component owns controlled or uncontrolled open state, disabled behavior, the modal popover, initial focus, Escape dismissal, focus return, and selection-driven close. Canvas subscriptions and available-item resolution live in the mounted popup content so a closed selector does not subscribe to workflow state.
|
||||
|
||||
@@ -12,25 +12,6 @@ Positioning uses the Dify UI popover API directly. Prefer `placement` alone. `si
|
||||
|
||||
Standalone selectors must declare `standalonePanel`. Availability props such as `noBlocks` only determine which tabs exist and must not change the layout mode implicitly.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- `index`: Public `BlockSelector` entry, popover lifecycle, trigger contract, focus management, and workflow-state adaptation.
|
||||
- `tabs`: Tab order, one-session filter state, and panel routing.
|
||||
- `tool-panel` and `tool-browser`: Installed-tool query adaptation and the complete tool browsing surface.
|
||||
- `blocks`, `data-sources`, and `all-start-blocks`: Domain panels for workflow nodes, data sources, and entry nodes.
|
||||
- `snippets`: Snippet filtering, insertion, list rows, and preview content.
|
||||
- `tool`, `trigger-plugin`, and `marketplace-plugin`: Row and list modules for installed and Marketplace integrations.
|
||||
- `featured-tools`, `featured-triggers`, and `rag-tool-recommendations`: Recommendation sections owned by this selector.
|
||||
- `hooks`, `storage`, `tool-list-data`, and `types`: Selector-specific state and data contracts; these are not public compatibility entrypoints.
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/plugins/marketplace`: Marketplace queries, search controls, categories, and URL construction.
|
||||
- `app/components/tools`: Installed tool contracts, permissions, and custom-tool creation.
|
||||
- `app/components/workflow`: Workflow node contracts, stores, node metadata, and insertion callbacks.
|
||||
- `features/system-features`: Marketplace feature availability.
|
||||
- `service/use-plugins`, `service/use-tools`, and `service/use-triggers`: Remote recommendation and installed-provider queries.
|
||||
|
||||
## Ownership
|
||||
|
||||
`BlockSelectorPanels` owns one mounted popup session. Each tab keeps independent search and tag state for that session. Closing the popup unmounts the session and resets those values.
|
||||
|
||||
@@ -179,7 +179,7 @@ const Panel: FC = () => {
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={() => setShowVariableInspectPanel(false)}
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<RiCloseLine aria-hidden className="size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="grow p-2">
|
||||
@@ -200,7 +200,7 @@ const Panel: FC = () => {
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
onClick={() => setShowVariableInspectPanel(false)}
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
<RiCloseLine aria-hidden className="size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="grow p-2">
|
||||
|
||||
+4
-16
@@ -18,7 +18,7 @@ pnpm check:fix
|
||||
|
||||
CI and local development use the same root `vite.config.ts` configuration.
|
||||
|
||||
For a smaller code scope, pass paths directly to Vite+:
|
||||
To narrow formatting and linting, pass paths directly to Vite+. Type checking remains repository-wide:
|
||||
|
||||
```sh
|
||||
vp check web/app/components packages/dify-ui/src/button
|
||||
@@ -65,7 +65,7 @@ Always review automatic fixes before committing. JS plugins are allowed to provi
|
||||
|
||||
### Type-aware Linting
|
||||
|
||||
The root configuration enables both `typeAware` and `typeCheck`, so `vp check` runs type-aware rules and full TypeScript diagnostics through the TypeScript Go toolchain.
|
||||
The root configuration enables both `typeAware` and `typeCheck`, so `vp check` runs type-aware rules and full diagnostics through the TypeScript 7 native compiler.
|
||||
|
||||
The web package still runs its existing TSSLint rule separately:
|
||||
|
||||
@@ -81,20 +81,13 @@ pnpm check
|
||||
|
||||
### Bulk Suppressions
|
||||
|
||||
Existing error diagnostics are tracked in two root files:
|
||||
|
||||
- `oxlint-suppressions.json` stores the Oxlint baseline.
|
||||
- `eslint-suppressions.json` stores the non-code ESLint baseline; declaration-file entries were removed when ESLint stopped processing code.
|
||||
|
||||
Each linter reports newly added errors beyond its recorded per-file rule baseline. Warnings remain visible and do not fail the normal lint command.
|
||||
Existing Oxlint error diagnostics are tracked in the root `oxlint-suppressions.json` baseline. Oxlint reports newly added errors beyond that per-file rule baseline. ESLint has no bulk-suppression baseline. Warnings remain visible and do not fail the normal lint command.
|
||||
|
||||
The bulk-suppression flags are available in the bundled Oxlint version but are currently hidden from `vp lint --help`. Run them from the repository root so every package uses the same baseline:
|
||||
|
||||
```sh
|
||||
pnpm lint:oxlint --suppress-all
|
||||
pnpm lint:oxlint --prune-suppressions
|
||||
pnpm lint:eslint --suppress-all
|
||||
pnpm lint:eslint --prune-suppressions
|
||||
```
|
||||
|
||||
The Oxc editor extension does not yet apply the bulk-suppression baseline, so the editor may still display findings that the CLI suppresses.
|
||||
@@ -119,8 +112,6 @@ Suppression comments belong to exactly one linter. Use `oxlint-disable` for code
|
||||
|
||||
Prefer a native Oxlint rule. If none exists, verify that the rule works through an Oxlint JS plugin on representative files. Record unsupported code rules as migration gaps instead of adding them to ESLint; reserve the ESLint configuration for non-code languages that Oxlint cannot parse. Do not add the Antfu ESLint config as a dependency or enable rules already covered by Oxlint.
|
||||
|
||||
For overlay import policy and composition rules, see [Overlay Guide].
|
||||
|
||||
## Type Checking
|
||||
|
||||
You should be able to see suggestions from TypeScript in your editor for all open files.
|
||||
@@ -131,7 +122,4 @@ Type checking is part of the repository check:
|
||||
pnpm check
|
||||
```
|
||||
|
||||
Type checking is powered by [`tsgo`] (the native TypeScript 7 compiler), which is significantly faster than `tsc`.
|
||||
|
||||
[Overlay Guide]: ./overlay.md
|
||||
[`tsgo`]: https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-beta
|
||||
Type checking is powered by the repository's `@typescript/native` dependency.
|
||||
|
||||
+22
-32
@@ -1,44 +1,34 @@
|
||||
# Overlay Best Practices
|
||||
|
||||
Use `@langgenius/dify-ui/*` primitives for overlays in new and modified web
|
||||
code. Do not import raw Base UI overlays or legacy web overlays from
|
||||
`@/app/components/base/modal`, `@/app/components/base/dialog`, or
|
||||
`@/app/components/base/drawer`.
|
||||
Use `@langgenius/dify-ui/*` primitives for ordinary overlays in new and modified web code. Choose by interaction semantics, not visual shape. A feature may compose business content around a primitive, but it must not recreate generic portal, backdrop, positioning, focus, or z-index behavior.
|
||||
|
||||
## Primitive choice
|
||||
|
||||
- Use `@langgenius/dify-ui/dialog` for modal surfaces that need focus
|
||||
management, scroll locking, escape handling, and outside-press dismissal.
|
||||
- Use `@langgenius/dify-ui/alert-dialog` only for destructive or must-confirm
|
||||
decisions.
|
||||
- Use `@langgenius/dify-ui/drawer` for side panels, setup panels, and nested
|
||||
editor panels that must behave like a drawer. Do not add separate web drawer
|
||||
wrappers.
|
||||
- Use `@langgenius/dify-ui/popover` or the web `Infotip` wrapper for
|
||||
explanatory content, long help text, rich layout, or interactive content.
|
||||
- Use `@langgenius/dify-ui/tooltip` only for short, non-interactive labels where
|
||||
the trigger already has its own accessible name.
|
||||
- Use `Dialog` for modal surfaces that need focus containment, scroll locking, Escape handling, and outside-press dismissal.
|
||||
- Use `AlertDialog` only when a destructive or must-confirm decision requires an explicit response.
|
||||
- Use `Drawer` for side panels and setup or editor surfaces that follow the drawer interaction model.
|
||||
- Use `DropdownMenu` for a button-triggered action list and `ContextMenu` for pointer-context actions.
|
||||
- Choose `Select`, `Combobox`, or `Autocomplete` by the [Dify UI search and picker contract], not because each renders a popup.
|
||||
- Use `Popover` or the web `Infotip` wrapper for explanatory content, long help text, rich layout, or interactive content.
|
||||
- Use `Tooltip` only for a short, non-interactive visual label. Its trigger must already have an accessible name.
|
||||
- Use `PreviewCard` only as a non-interactive visual preview of a link destination. Essential information must remain available without the preview.
|
||||
|
||||
## Preferences
|
||||
## Composition
|
||||
|
||||
- Prefer the most specific semantic primitive over styling a generic `Dialog`.
|
||||
- Prefer controlled `open` / `onOpenChange` when business state, analytics, or
|
||||
cleanup must react to open state changes.
|
||||
- Prefer the primitive-owned portal or content component. Do not create manual
|
||||
portals around overlay primitives.
|
||||
- Prefer native button trigger semantics. When passing a Base UI trigger
|
||||
`render` prop, render a real `<button type="button">` for button-like
|
||||
triggers; use `nativeButton={false}` only for intentional non-button triggers.
|
||||
- Use `Infotip` for visible `?` help triggers. Give icon-only triggers an
|
||||
accessible name.
|
||||
- Keep overlay chrome inside the shared primitive or business wrapper instead of
|
||||
repeating backdrop, z-index, and portal styles at call sites.
|
||||
- Use controlled `open` and `onOpenChange` when business state, analytics, or cleanup reacts to visibility; otherwise let the primitive own its state.
|
||||
- For a button-like overlay trigger, keep the state-owning primitive outside and use its `render` prop to render the final `Button` or `IconButton`. Do not mirror open, pressed, or expanded state in the button.
|
||||
- Use the primitive-owned content or portal part. Do not wrap a Dify UI overlay in another manual portal.
|
||||
- Keep shared overlay chrome in Dify UI and feature-specific content in the feature owner.
|
||||
|
||||
## Feature-owned exception
|
||||
|
||||
The Step-by-step Tour coachmark is a deliberate feature-owned overlay because it targets arbitrary route content and owns spotlight geometry, pointer blockers, and target interaction policy. Its manual portal belongs to `web/app/components/step-by-step-tour/coachmark.tsx`; it is not a general overlay primitive or a pattern for ordinary dialogs and popovers.
|
||||
|
||||
## Layering
|
||||
|
||||
All body-portalled Dify UI overlays use `z-50`. Toast uses `z-60`. The app root
|
||||
must keep an isolated stacking context.
|
||||
Body-portalled Dify UI overlays use `z-50`; Toast uses `z-60`. The app root keeps an isolated stacking context, and overlays at the same layer rely on DOM order.
|
||||
|
||||
Do not add call-site z-index overrides such as `z-9999`. If an overlay is
|
||||
clipped or hidden, fix the parent overlay structure instead of raising the
|
||||
child primitive.
|
||||
Do not add call-site z-index overrides such as `z-9999`. If an overlay is clipped or hidden, fix the owning overlay structure instead of raising a child primitive.
|
||||
|
||||
[Dify UI search and picker contract]: ../../packages/dify-ui/README.md#search-and-picker-selection
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
## Agent V2 Frontend
|
||||
|
||||
- Keep Agent V2 separate from legacy workflow Agent. Use `web/features/agent-v2`, `web/app/components/workflow/nodes/agent-v2`, the `agent_node_kind: 'dify_agent'` and `version: '2'` payload discriminator, and `BlockEnum.AgentV2` where the graph type is already migrated. Do not bridge Agent V2 to legacy `agent_strategy_*` behavior or data shapes.
|
||||
- `agent-composer` owns editable Agent configuration state. `agent-detail/configure` composes that state with server synchronization, build draft commands, preview chat sessions, version viewing, and workspace panels; it must not create a second configuration store.
|
||||
- Configure overlays stay with the feature component that owns the interaction and compose Dify UI primitives. Existing `ModalContext` consumers are migration boundaries, not an API for new Agent V2 dialogs.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# Agent Configure
|
||||
|
||||
Owns the Agent V2 configure runtime used by the Agent App configure page and workflow inline Agent configure surface, including editable composer draft wiring, build chat sessions, version viewing, build draft mode, and preview side panels.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- agent-composer
|
||||
- agent-detail/configure/state
|
||||
- agent-detail/configure/use-agent-build-draft-run
|
||||
- agent-detail/configure/use-agent-configure-build-draft
|
||||
- agent-detail/configure/use-agent-configure-sync
|
||||
- agent-detail/configure/components/orchestrate
|
||||
- agent-detail/configure/components/preview
|
||||
- agent-detail/configure/components/workspace
|
||||
- agent-detail/configure/model-compatibility
|
||||
|
||||
## External Modules
|
||||
|
||||
- app/components/base/chat
|
||||
- app/components/base/app-icon
|
||||
- app/components/base/features
|
||||
- app/components/base/file-uploader
|
||||
- app/components/base/infotip
|
||||
- app/components/base/loading
|
||||
- app/components/base/prompt-editor
|
||||
- app/components/base/skeleton
|
||||
- app/components/app/configuration/config/agent/agent-tools
|
||||
- app/components/datasets
|
||||
- app/components/header/account-setting/model-provider-page
|
||||
- app/components/plugins
|
||||
- app/components/tools
|
||||
- app/components/workflow/block-icon
|
||||
- app/components/workflow/block-selector
|
||||
- app/components/workflow/hooks/use-serial-async-callback
|
||||
- app/components/workflow/nodes
|
||||
- app/components/workflow/types
|
||||
- config
|
||||
- context/app-context
|
||||
- context/i18n
|
||||
- context/modal-context
|
||||
- @dify/contracts/api/console/router.gen
|
||||
- hooks/use-format-time-from-now
|
||||
- hooks/use-theme
|
||||
- hooks/use-timestamp
|
||||
- models/datasets
|
||||
- models/debug
|
||||
- models/log
|
||||
- service/base
|
||||
- service/use-common
|
||||
- types/app
|
||||
- types/common
|
||||
- types/i18n
|
||||
- types/workflow
|
||||
- utils/format
|
||||
- utils/var
|
||||
@@ -1,55 +1,10 @@
|
||||
# New RAG
|
||||
|
||||
Renders the KnowledgeFS-backed knowledge list, creation flows, sources, documents, and processing
|
||||
tasks.
|
||||
This feature owns the KnowledgeFS-backed knowledge list, creation flows, sources, documents, revisions, and processing tasks.
|
||||
|
||||
## Internal Modules
|
||||
- `routes.ts` provides feature route construction; new or modified navigation must consume it instead of rebuilding paths.
|
||||
- Document query modules own server state. View components receive query results and user commands rather than mirroring remote state.
|
||||
- Processing task events are normalized by the feature service and coordinated by the task observer and progress store.
|
||||
- Exit confirmation, creation, and processing overlays are feature compositions of Dify UI Dialog, AlertDialog, Drawer, and Popover primitives.
|
||||
|
||||
- `add-source-page`
|
||||
- `components/create-knowledge-dialog-parts`
|
||||
- `components/create-knowledge-exit-dialog`
|
||||
- `components/knowledge-space-card`
|
||||
- `components/knowledge-view-switcher`
|
||||
- `components/new-knowledge-list-states`
|
||||
- `crawl-selection-form`
|
||||
- `create-knowledge-page`
|
||||
- `create-upload-queue`
|
||||
- `create-knowledge-workflow`
|
||||
- `document-list`
|
||||
- `document-chunk-detail`
|
||||
- `document-chunk-tree`
|
||||
- `document-detail-header`
|
||||
- `document-detail-model`
|
||||
- `document-detail-queries`
|
||||
- `document-detail-page`
|
||||
- `document-detail-status`
|
||||
- `document-revision-content`
|
||||
- `document-model`
|
||||
- `document-upload-policy`
|
||||
- `documents-page`
|
||||
- `knowledge-route-guard`
|
||||
- `knowledge-space-shell`
|
||||
- `new-knowledge-list`
|
||||
- `processing-tasks-drawer`
|
||||
- `routes`
|
||||
- `services/processing-task-events`
|
||||
- `sources-page`
|
||||
- `storage`
|
||||
- `task-event-observer`
|
||||
- `use-document-task-status`
|
||||
- `use-document-reindex`
|
||||
- `website-crawl-preview`
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/base/corner-label`
|
||||
- `app/components/base/loading`
|
||||
- `app/components/base/search-input`
|
||||
- `app/components/base/skeleton`
|
||||
- `app/components/datasets/external-api/external-api-panel`
|
||||
- `app/components/datasets/extra-info/service-api`
|
||||
- `context/external-api-panel-context`
|
||||
- `context/permission-state`
|
||||
- `hooks/use-format-time-from-now`
|
||||
- `service/knowledge/use-dataset`
|
||||
- `utils/permission`
|
||||
Files in this directory remain feature-owned; direct consumers do not become their owners. Keep shared dataset APIs and permission policy in their existing owners rather than copying them into this feature.
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
# System Features
|
||||
|
||||
Loads deployment-wide capabilities for server prefetching, client hydration, and application bootstrap.
|
||||
This feature owns the deployment-wide capability query contract and application bootstrap boundary.
|
||||
|
||||
## Internal Modules
|
||||
- `server.ts` exposes the request-scoped query client and canonical server query options; route and layout owners perform prefetching and hydration.
|
||||
- `client.ts` owns the canonical query options.
|
||||
- `bootstrap-boundary.tsx` blocks application rendering until capability data is available and owns retry UI.
|
||||
- `state.ts` exposes narrow derived client atoms backed by the same TanStack Query data.
|
||||
|
||||
- `bootstrap-boundary.tsx`
|
||||
- `client.ts`
|
||||
- `server.ts`
|
||||
- `state.ts`
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/get-query-client`
|
||||
- `app/components/full-screen-loading`
|
||||
- `service/client`
|
||||
- `service/server`
|
||||
- `utils/client`
|
||||
- `utils/query-atoms`
|
||||
Consumers read capabilities through this feature instead of issuing another request or copying deployment-edition state.
|
||||
|
||||
+33
-179
@@ -1,189 +1,43 @@
|
||||
# Internationalization (i18n)
|
||||
# Internationalization
|
||||
|
||||
## Introduction
|
||||
English JSON files under `web/i18n/en-US/` are the source locale. Other locale directories must keep the same flat keys and placeholders. i18next uses `keySeparator: false`, so dots are part of a key rather than nested-object separators.
|
||||
|
||||
This directory contains i18n tooling and configuration. Translation files live under `web/i18n`.
|
||||
## Owners
|
||||
|
||||
## File Structure
|
||||
- `languages.ts` is the source of truth for supported Web locales.
|
||||
- `language.ts` owns locale normalization and product-specific locale mappings.
|
||||
- `resources.ts` owns the typed namespace registry. File names use kebab case while namespaces use camel case, for example `app-debug.json` and `appDebug`.
|
||||
- `locale-resources/<locale>.ts` owns lazy loading for one locale.
|
||||
- `settings.ts` owns shared i18next options.
|
||||
- `web/scripts/check-i18n.js` owns locale key validation and removal of extra keys.
|
||||
|
||||
```txt
|
||||
web/i18n
|
||||
├── en-US
|
||||
│ ├── app.json
|
||||
│ ├── app-debug.json
|
||||
│ ├── common.json
|
||||
│ └── ...
|
||||
└── zh-Hans
|
||||
└── ...
|
||||
Do not copy the language registry into documentation. Read the current source files when adding a locale or namespace.
|
||||
|
||||
web/i18n-config
|
||||
├── language.ts
|
||||
├── i18next-config.ts
|
||||
└── ...
|
||||
## Add a locale
|
||||
|
||||
1. Add the locale metadata to `languages.ts`.
|
||||
2. Add a matching `web/i18n/<locale>/` directory with every source namespace.
|
||||
3. Add `locale-resources/<locale>.ts` and the required mappings in `language.ts`.
|
||||
4. Keep the backend language and timezone registry in `api/constants/languages.py` aligned when the locale is accepted by backend APIs.
|
||||
5. Run the complete i18n check before submitting the change.
|
||||
|
||||
`LanguagesSupported` is populated from the supported entries in `languages.ts`. `language.ts` also owns the accepted locale spellings and the `I18nText` contract; keep them aligned when adding a locale.
|
||||
|
||||
## Add or change copy
|
||||
|
||||
Add or change the English key first, then update every supported locale. Preserve interpolation variables and markup placeholders exactly.
|
||||
|
||||
Run from `web/`:
|
||||
|
||||
```sh
|
||||
pnpm i18n:check
|
||||
pnpm i18n:check --file app billing --lang zh-Hans ja-JP
|
||||
```
|
||||
|
||||
We use English as the default language. Translation files are organized by language and then by module. For example, the English translation for the `app` module is in `web/i18n/en-US/app.json`.
|
||||
Arguments after `--file` and `--lang` are space-separated. Use `--auto-remove` only when intentionally deleting extra locale keys.
|
||||
|
||||
Translation files are JSON with flat keys (dot notation). i18next is configured with `keySeparator: false`, so dots are part of the key. The namespace is the camelCase file name (for example, `app-debug.json` -> `appDebug`), so use `useTranslation('appDebug')` or `t('key', { ns: 'appDebug' })`.
|
||||
## Automated translation
|
||||
|
||||
If you want to add a new language or modify an existing translation, create or update the `.json` files in the language folder.
|
||||
Changes to `web/i18n/en-US/*.json` on `main` trigger the scoped translation workflow. The workflow derives target locales from `languages.ts`, translates only the changed namespaces and keys, verifies them with `i18n:check`, and opens a pull request when translations change.
|
||||
|
||||
For example, if you want to add French translation, you can create a new folder `fr-FR` and add the translation files in it.
|
||||
|
||||
By default we will use `LanguagesSupported` to determine which languages are supported. For example, in login page and settings page, we will use `LanguagesSupported` to determine which languages are supported and display them in the language selection dropdown.
|
||||
|
||||
## Example
|
||||
|
||||
1. Create a new folder for the new language.
|
||||
|
||||
```txt
|
||||
cd web/i18n
|
||||
cp -r en-US id-ID
|
||||
```
|
||||
|
||||
1. Modify the translation `.json` files in the new folder. Keep keys flat (for example, `dialog.title`).
|
||||
|
||||
1. Add the new language to the `languages.ts` file.
|
||||
|
||||
```typescript
|
||||
export const languages = [
|
||||
{
|
||||
value: 'en-US',
|
||||
name: 'English(United States)',
|
||||
example: 'Hello, Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'zh-Hans',
|
||||
name: '简体中文',
|
||||
example: '你好,Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'pt-BR',
|
||||
name: 'Português(Brasil)',
|
||||
example: 'Olá, Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'es-ES',
|
||||
name: 'Español(España)',
|
||||
example: 'Saluton, Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'fr-FR',
|
||||
name: 'Français(France)',
|
||||
example: 'Bonjour, Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'de-DE',
|
||||
name: 'Deutsch(Deutschland)',
|
||||
example: 'Hallo, Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'ja-JP',
|
||||
name: '日本語 (日本)',
|
||||
example: 'こんにちは、Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'ko-KR',
|
||||
name: '한국어 (대한민국)',
|
||||
example: '안녕, Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'ru-RU',
|
||||
name: 'Русский(Россия)',
|
||||
example: 'Привет, Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'it-IT',
|
||||
name: 'Italiano(Italia)',
|
||||
example: 'Ciao, Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'th-TH',
|
||||
name: 'ไทย(ประเทศไทย)',
|
||||
example: 'สวัสดี Dify!',
|
||||
supported: false,
|
||||
},
|
||||
{
|
||||
value: 'id-ID',
|
||||
name: 'Bahasa Indonesia',
|
||||
example: 'Halo, Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'uk-UA',
|
||||
name: 'Українська(Україна)',
|
||||
example: 'Привет, Dify!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'fa-IR',
|
||||
name: 'Farsi (Iran)',
|
||||
example: 'سلام, دیفای!',
|
||||
supported: true,
|
||||
},
|
||||
{
|
||||
value: 'ar-TN',
|
||||
name: 'العربية (تونس)',
|
||||
example: 'مرحبا، Dify!',
|
||||
supported: true,
|
||||
},
|
||||
// Add your language here 👇
|
||||
// ...
|
||||
// Add your language here 👆
|
||||
]
|
||||
```
|
||||
|
||||
1. Don't forget to mark the supported field as `true` if the language is supported.
|
||||
|
||||
1. Sometimes you might need to do some changes in the server side. Please change this file as well. 👇
|
||||
<https://github.com/langgenius/dify/blob/61e4bbabaf2758354db4073cbea09fdd21a5bec1/api/constants/languages.py#L5>
|
||||
|
||||
> Note: `I18nText` type is automatically derived from `LanguagesSupported`, so you don't need to manually add types.
|
||||
|
||||
## Clean Up
|
||||
|
||||
That's it! You have successfully added a new language to the project. If you want to remove a language, you can simply delete the folder and remove the language from the `languages.ts` file.
|
||||
|
||||
We have a list of languages that we support in the `languages.ts` file. But some of them are not supported yet. So, they are marked as `false`. If you want to support a language, you can follow the steps above and mark the supported field as `true`.
|
||||
|
||||
## Utility scripts
|
||||
|
||||
- Check missing/extra keys: `pnpm run i18n:check --file app billing --lang zh-Hans [--auto-remove]`
|
||||
- Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys; `--auto-remove` deletes extra keys automatically.
|
||||
|
||||
## Automatic Translation
|
||||
|
||||
Translation is handled automatically by Claude Code GitHub Actions. When changes are pushed to `web/i18n/en-US/*.json` on the main branch:
|
||||
|
||||
1. Claude Code analyzes the git diff to detect changes
|
||||
1. Identifies three types of changes:
|
||||
- **ADD**: New keys that need translation
|
||||
- **UPDATE**: Modified keys that need re-translation (even if target language has existing translation)
|
||||
- **DELETE**: Removed keys that need to be deleted from other languages
|
||||
1. Runs `i18n:check` to verify the initial sync status.
|
||||
1. Translates missing/updated keys while preserving placeholders (`{{var}}`, `${var}`, `<tag>`) and removes deleted keys.
|
||||
1. Runs `vp fmt` on edited locale files and `i18n:check` again to ensure everything is synchronized.
|
||||
1. Creates a PR with the translations.
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
To manually trigger translation:
|
||||
|
||||
1. Go to Actions > "Translate i18n Files with Claude Code"
|
||||
1. Click "Run workflow"
|
||||
1. Optionally configure:
|
||||
- **files**: Specific files to translate (space-separated, e.g., "app common")
|
||||
- **languages**: Specific languages to translate (space-separated, e.g., "zh-Hans ja-JP")
|
||||
- **mode**: `incremental` (default, only changes) or `full` (check all keys)
|
||||
|
||||
Workflow: `.github/workflows/translate-i18n-claude.yml`
|
||||
Use the `Translate i18n Files with Claude Code` workflow dispatch for a manual scoped sync. Full mode requires an explicit file list.
|
||||
|
||||
Reference in New Issue
Block a user