diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md
index 5cb860b..cfd56cc 100644
--- a/docs/advanced/architecture.md
+++ b/docs/advanced/architecture.md
@@ -50,6 +50,26 @@ This question is best answered by tracing what happens when a user (you!) runs `
2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts.
3. A separate `"render"` event can be dispatched when the DOM is updated in-place without a full navigation (e.g. after content decryption). Components that attach listeners to content elements should listen for both `"nav"` and `"render"`.
+## Community Package Layering
+
+Quartz v5 separates shared code into three community packages, each with a distinct responsibility:
+
+- **`@quartz-community/types`** — Type definitions, interfaces, and the canonical `vfile` DataMap augmentation. This is the "contract" between Quartz and plugins. It has no runtime dependencies.
+- **`@quartz-community/utils`** — Shared utility functions (path manipulation, DOM helpers, sorting, date formatting, JSX conversion, etc.). Depends on `@quartz-community/types`.
+- **`@quartz-community/runtime`** — Browser-only utilities for client-side scripts (event handling, navigation, storage, script loading). Depends on both `types` and `utils`.
+
+```
+types (no deps)
+ ↑
+utils (depends on types)
+ ↑
+runtime (depends on types + utils)
+ ↑
+plugins (depend on any combination)
+```
+
+Plugins should import types from `@quartz-community/types`, utility functions from `@quartz-community/utils`, and browser utilities from `@quartz-community/runtime`. This layering ensures plugins don't depend on Quartz core.
+
## Plugin System
Page types define how a category of pages is rendered. They are configured in the `pageTypes` array in `quartz.config.yaml`.
@@ -64,6 +84,9 @@ There are now four plugin categories:
- **Filters**: Filter content (remove drafts, explicit publish)
- **Emitters**: Reduce over content (generate RSS, sitemaps, alias redirects, OG images)
- **Page Types**: Define how pages are rendered. Each page type handles a specific kind of page (content notes, folder listings, tag listings, 404). The `PageTypeDispatcher` emitter routes pages to the appropriate page type plugin based on the content.
+- **Bases Views**: Custom view renderers for the `bases-page` plugin's database-like view system. Plugins can register new view types (e.g., timeline, kanban) via the `ViewRegistry`. See [[making plugins#Bases Views]] for details.
+
+Note that plugin types are **not mutually exclusive** — a single plugin can be a transformer AND provide components (e.g., `obsidian-flavored-markdown`), or be a page type AND provide custom frames (e.g., `canvas-page`).
### Plugin Resolution
diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md
index 6a4d57b..bdffa40 100644
--- a/docs/advanced/creating components.md
+++ b/docs/advanced/creating components.md
@@ -109,6 +109,23 @@ Component.css = styles
> [!warning]
> Quartz does not use CSS modules so any styles you declare here apply _globally_. If you only want it to apply to your component, make sure you use specific class names and selectors.
+### Internationalization
+
+Component plugins should use the i18n pattern for any user-facing strings. See [[making plugins#Internationalization (i18n)]] for the full setup guide.
+
+Quick reference:
+
+```tsx
+import { i18n } from "../i18n"
+
+const MyComponent: QuartzComponent = ({ cfg }) => {
+ const t = i18n(cfg.locale ?? "en-US").components.myComponent
+ return
{t.title}
+}
+```
+
+Always provide at least an `en-US` locale as the fallback. Additional locales are optional but encouraged for international reach.
+
### Scripts and Interactivity
For interactivity, you can declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties on the component. These should be strings containing the JavaScript to be executed in the browser.
diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md
index cf9aae4..c92f0ee 100644
--- a/docs/advanced/making plugins.md
+++ b/docs/advanced/making plugins.md
@@ -63,6 +63,25 @@ The plugin's `package.json` should declare dependencies on `@quartz-community/ty
## Plugin Types
+## Choosing a Plugin Type
+
+Quartz supports six plugin capabilities. A single plugin can combine multiple types.
+
+| I want to... | Plugin Type |
+| ------------------------------------------------ | ----------- |
+| Transform Markdown/HTML content | Transformer |
+| Decide which pages to publish | Filter |
+| Generate output files (RSS, sitemaps, manifests) | Emitter |
+| Define how a category of pages renders | Page Type |
+| Add a UI component to the layout | Component |
+| Add a custom view to the Bases database system | Bases View |
+
+These are **not mutually exclusive**. For example:
+
+- `obsidian-flavored-markdown` is both a **transformer** (processes OFM syntax) and provides **components** (mermaid rendering)
+- `canvas-page` is a **page type** that also provides a custom **frame**
+- A plugin could be a **transformer** that adds metadata AND a **component** that displays it
+
### Transformers
Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself.
@@ -466,6 +485,40 @@ When a user installs your plugin, Quartz automatically loads the frame from the
> [!tip]
> See the [`canvas-page`](https://github.com/quartz-community/canvas-page) plugin for a complete real-world example of a plugin-provided frame.
+### Bases Views
+
+The `bases-page` plugin provides a database-like view system similar to Obsidian Bases. Other plugins can register custom view types via the `ViewRegistry`:
+
+```ts
+import { viewRegistry } from "@quartz-community/bases-page";
+import type { ViewTypeRegistration } from "@quartz-community/bases-page";
+
+viewRegistry.register({
+ id: "timeline",
+ name: "Timeline",
+ icon: "git-branch",
+ render: ({ entries, view, slug, allSlugs }) => (
+
+ {entries.map(entry =>
{entry.properties.title}
)}
+
+ ),
+ css: `.bases-timeline { display: flex; flex-direction: column; }`,
+ afterDOMLoaded: `document.addEventListener("nav", () => { /* setup */ })`,
+});
+```
+
+Each view registration includes:
+
+- `id`: Unique identifier (e.g., `"timeline"`, `"kanban"`)
+- `name`: Display name shown in the view selector
+- `icon`: Optional Lucide icon name
+- `render`: Function that receives `ViewRendererProps` and returns Preact JSX
+- `css`: Optional CSS string (deduplicated by view ID)
+- `afterDOMLoaded`: Optional client-side script (same lifecycle as component scripts)
+- `options`: Optional configuration passed to every render invocation
+
+The `ViewRegistry` is a global singleton (via `Symbol.for`) ensuring all copies of the module share the same registry.
+
## Building and Testing
```shell
@@ -475,6 +528,108 @@ npm run build
npx tsup
```
+## What to Import from Where
+
+| You need... | Import from |
+| ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
+| Type definitions (`QuartzTransformerPlugin`, `QuartzComponent`, etc.) | `@quartz-community/types` |
+| Path utilities (`simplifySlug`, `resolveRelative`, `pathToRoot`) | `@quartz-community/utils/path` |
+| DOM utilities (`removeAllChildren`, `registerEscapeHandler`) | `@quartz-community/utils/dom` |
+| JSX conversion (`htmlToJsx`) | `@quartz-community/utils/jsx` |
+| Language utilities (`classNames`, `capitalize`) | `@quartz-community/utils/lang` |
+| Date/sort utilities (`formatDate`, `getDate`, `byDateAndAlphabetical`) | `@quartz-community/utils/date` and `@quartz-community/utils/sort` |
+| HTML escaping (`escapeHTML`, `unescapeHTML`) | `@quartz-community/utils/escape` |
+| Emoji utilities (`getIconCode`) | `@quartz-community/utils/emoji` |
+| Browser runtime (`onNav`, `onRender`, `fetchContentIndex`) | `@quartz-community/runtime` |
+
+Do **not** import from `@jackyzha0/quartz` or from `vfile` directly. Use the community packages instead.
+
+## Internationalization (i18n)
+
+Plugins should provide their own translations for user-facing strings. Do **not** hardcode strings in components.
+
+### Setting Up i18n
+
+Create the following structure:
+
+```
+src/i18n/
+├── index.ts
+└── locales/
+ └── en-US.ts
+```
+
+**`src/i18n/locales/en-US.ts`** (required base locale):
+
+```ts
+export default {
+ components: {
+ myPlugin: {
+ title: "My Plugin",
+ description: "A description",
+ itemCount: ({ count }: { count: number }) => (count === 1 ? "1 item" : `${count} items`),
+ },
+ },
+}
+```
+
+**`src/i18n/index.ts`**:
+
+```ts
+import enUS from "./locales/en-US"
+
+const locales: Record = {
+ "en-US": enUS,
+}
+
+export function i18n(locale: string) {
+ return locales[locale] || enUS
+}
+```
+
+### Using i18n in Components
+
+```tsx
+import { i18n } from "../i18n"
+
+const MyComponent: QuartzComponent = ({ cfg }) => {
+ const locale = cfg.locale ?? "en-US"
+ const t = i18n(locale).components.myPlugin
+ return {t.title}
+}
+```
+
+### Adding Translations
+
+To add a new locale, copy `en-US.ts`, translate the strings, and register it:
+
+```ts
+// src/i18n/locales/fr-FR.ts
+export default {
+ components: {
+ myPlugin: {
+ title: "Mon Plugin",
+ description: "Une description",
+ itemCount: ({ count }: { count: number }) =>
+ count === 1 ? "1 élément" : `${count} éléments`,
+ },
+ },
+}
+```
+
+```ts
+// src/i18n/index.ts
+import enUS from "./locales/en-US"
+import frFR from "./locales/fr-FR"
+
+const locales: Record = {
+ "en-US": enUS,
+ "fr-FR": frFR,
+}
+```
+
+Use [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag) locale codes (e.g., `en-US`, `de-DE`, `ja-JP`, `zh-CN`). For dynamic content, use function-based translations as shown with `itemCount` above.
+
## Installing Your Plugin
```shell