Skip to main content

polystella-consumer

Add or maintain PolyStella in an existing Astro project. Use when integrating AI-driven build-time content localization, configuring R2 caching, wiring locale-prefixed routes, or debugging an existing PolyStella setup in a downstream project.

Datos de origen

Repositorio
cloudflare/polystella
Última actividad en el origen
21 de septiembre de 2026 a las 14:08
Idioma detectado de SKILL.md
inglés
Estrellas
16
Forks
2

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
polystella-consumer
description
Add or maintain PolyStella in an existing Astro project. Use when integrating AI-driven build-time content localization, configuring R2 caching, wiring locale-prefixed routes, or debugging an existing PolyStella setup in a downstream project.
# polystella-consumer You are working in an Astro project that consumes the `@cloudflare/polystella-astro` package. This skill covers integration, configuration, common pitfalls, and the debug flow. If you are working on the `polystella` package source itself (adding adapters, editing translators, modifying the cache layer), STOP and load `polystella-contributor` instead. ## What PolyStella does Build-time content localization for Astro: - **Translates** content collections (`.md`, `.mdx`, `.toml`, `.json`, `.yaml`) into additional locales using AI (Workers AI or Anthropic). - **Caches** translations in Cloudflare R2, content-addressed by source bytes + glossary + model. Unchanged content costs zero on rebuild. - **Injects routes** under `/[lang]/...` for each non-default locale. - **Provides runtime APIs** on `Astro.locals` (`t`, `lhref`, `getLocalizedEntry`, `getLocalizedCollection`) and React hooks (`useTranslations`, `useLocalizedHref`). - **Maintains UI strings** via per-locale JSON dicts with drift detection, sync, and AI-fill subcommands. Visitors get static bytes — no runtime AI calls. ## Installation Install from npm: ```bash pnpm add @cloudflare/polystella-astro ``` The standalone CLI binary is still named `polystella`. Peer dependency: `astro ^7.0.10`. ## Four-file integration Set up these four files. The locale set lives in `astro.config.mjs` ONLY — everything else derives from it. ### 1. `astro.config.mjs` ```js import { defineConfig } from "astro/config"; import sitemap from "@astrojs/sitemap"; import polystella, { astroSitemapI18n } from "@cloudflare/polystella-astro"; import polystellaConfig from "./polystella.config.mjs"; // Hoist `i18n` so the same object feeds Astro routing, PolyStella // translation, AND the sitemap helper. One source of truth. const i18n = { defaultLocale: "en-US", locales: ["en-US", "pt-BR", "ja-JP"], routing: { prefixDefaultLocale: false }, }; export default defineConfig({ i18n, integrations: [sitemap(astroSitemapI18n(i18n, { hreflang: { en: "en-US" } })), polystella(polystellaConfig)], }); ``` ### 2. `polystella.config.mjs` Where provider, glossary, R2, format-specific keys live. The schema source of truth is `packages/astro/src/config/options.ts` in the repository; everything is zod-validated at the boundary. Skeleton: ```js import "dotenv/config"; export default { // R2 config (branch-dispatched — see "Branch-isolated cache" below) r2: { accountId: process.env.R2_ACCOUNT_ID, accessKeyId: process.env.R2_ACCESS_KEY_ID, secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, bucket: "your-bucket-name", prefix: "i18n/", // readOnly: derived from environment; see below }, provider: { kind: "workers-ai", accountId: process.env.CLOUDFLARE_ACCOUNT_ID, apiToken: process.env.CLOUDFLARE_API_TOKEN, model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", // maxTokens: 8192, // DON'T LOWER — see Pitfall #3 }, // Per-locale model override (optional): // model: { default: "@cf/meta/...", "ja-JP": "@cf/qwen/..." }, glossaryDir: "i18n/glossary", // YAML files: <locale>.yaml overridesDir: "i18n/overrides", // <locale>/<mirrored-path> // Translatable frontmatter keys per source glob: frontmatter: { "content/publications/**/*.md": ["title", "description"], "content/people/**/*.md": ["position", "bio"], }, // Per-batch document-context framing (improves cross-section consistency): markdown: { contextKeys: { "content/publications/**/*.md": ["title", "excerpt"], }, }, // Locale-prefixed routes — list any pages that need shimming: routes: [ "src/pages/index.astro", "src/pages/[slug].astro", // { source: "src/pages/[slug].astro", imports: ["./src/styles/publication.css"] }, ], // CSS imports for shims (see Pitfall #5): routesImports: ["./src/styles/global.css"], }; ``` ### 3. `src/content.config.ts` ```ts import { defineCollection } from "astro:content"; import { polystellaCollections } from "@cloudflare/polystella-astro/content"; import { i18nLoader, i18nSchema } from "@cloudflare/polystella-astro/i18n"; import { publications, people } from "./content-schemas"; export const collections = { ...polystellaCollections({ source: { publications, people }, }), // Hand-authored UI-strings collection, drift-detected at build: i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }), }; ``` ### 4. `src/env.d.ts` ```ts /// <reference types="@cloudflare/polystella-astro/client" /> ``` Picks up types for PolyStella's virtual modules (`polystella:runtime-config`). ## Direct package use Use the lower-level packages when Astro should not own the operation: ```text source/record -> adapter -> core -> provider -> core -> adapter -> output ``` - Import translation contracts, glossaries, prompts, batching, and `PermanentProviderError` from `@cloudflare/polystella-core`. - Import portable format adapters from `@cloudflare/polystella-core/adapters`. - Import Workers AI and Anthropic factories from `@cloudflare/polystella-core/providers` or its provider subpaths. These packages use standard Web APIs and run in Workers without `nodejs_compat`; enabling `nodejs_compat` is also supported. The Astro package has no compatibility shims for low-level imports that moved to these owners. ## UI strings Chrome text (nav, footer, accessibility strings) lives in `src/content/i18n/<locale>.json` as flat key→string dicts. The default-locale file is the single source of truth; non-default locales must match its key set. Workflow: 1. Edit `src/content/i18n/en-US.json` — add, remove, or change keys. 2. Run `polystella translate-ui` to propagate changes through other locales. 3. Spot-check translations; hand-edit any keys where you want exact wording. The AI step only fills _empty_ values, so a hand-written value stays untouched on subsequent runs. 4. Commit. The pre-commit hook should run `polystella check-ui` automatically when `src/content/i18n/` is staged. Wire the pre-commit hook (`.githooks/pre-commit`): ```sh if printf '%s\n' "$STAGED" | grep -qE '^src/content/i18n/'; then pnpm exec polystella check-ui fi ``` ## Catalog-Only Adoption <a id="catalog-only-adoption"></a> Use this path when a project wants PolyStella's JSON catalog flow but already owns content localization, routing, or collection lookups. It does not register translated content collections, route shims, R2 cache clients, or localized collection APIs. Astro integration: ```ts import { defineConfig } from "astro/config"; import catalogAstro from "@cloudflare/polystella-astro/catalog/astro"; export default defineConfig({ i18n: { defaultLocale: "en-US", locales: ["en-US", "pt-BR", "ja-JP"], }, integrations: [ catalogAstro({ baseDir: "./src/i18n", middleware: true, driftCheck: true, }), ], }); ``` Manual middleware with explicit dictionaries: ```ts import { catalogMiddleware } from "@cloudflare/polystella-astro/catalog/middleware"; export const onRequest = catalogMiddleware({ defaultLocale: "en-US", locales: ["en-US", "pt-BR"], getDictionary: async (locale) => { if (locale === "en-US") return (await import("./i18n/en-US.json")).default; if (locale === "pt-BR") return (await import("./i18n/pt-BR.json")).default; return undefined; }, }); ``` The catalog middleware binds only `Astro.locals.t` and `Astro.locals.lhref`. The CLI commands still apply; use `--base` if the catalog directory is not `./src/content/i18n`: ```sh polystella check-ui --base ./src/i18n polystella sync-ui --base ./src/i18n polystella translate-ui --base ./src/i18n ``` Catalog files accept two formats, auto-detected per file (all locale files must use the same format): - Flat — `{ "site.title": "Cloudflare Blog" }`. - Nested — groups flattened to the same dotted keys, so `t("site.title")` works identically in both formats: ```json { "site": { "i18n_group_title": "Site", "title": "Cloudflare Blog" } } ``` `i18n_group_title` is optional per-group metadata for tooling that reads the JSON; it is excluded from `t()` keys, drift checks, and AI translation. ## Runtime APIs In `.astro` files: ```astro --- const { t, lhref, getLocalizedEntry, getLocalizedCollection } = Astro.locals; const { slug } = Astro.params; const entry = await getLocalizedEntry("publications", slug); const activePeople = await getLocalizedCollection( "people", ({ data }) => data.type === "active", ); --- <a href={lhref("/foo")}>{t("nav.foo")}</a> ``` Outside `.astro` (utility scripts, getStaticPaths, React islands): ```ts import { getLocalizedEntry, getLocalizedCollection, localizedHref } from "@cloudflare/polystella-astro/runtime"; import { useTranslations, useLocalizedHref } from "@cloudflare/polystella-astro/react"; import { getDictionary } from "@cloudflare/polystella-astro/i18n"; ``` ## Branch-isolated R2 cache Three modes, dispatched automatically by `polystella.config.mjs`: | Mode | Env signals | r2.prefix | Writes? | | :----------------- | :----------------------------------- | :----------------------------------- | :------------------------ | | Local build | neither var set | `i18n/` (read-only against main) | NO | | CI build (main) | `WORKERS_CI_BRANCH=main` | `i18n/` | YES | | CI build (preview) | `WORKERS_CI_BRANCH=<other>` | `previews/<branch>/i18n/` + fallback | YES (preview prefix only) | | Explicit CLI | `POLYSTELLA_CLI=1` (set by `cli.ts`) | per resolved branch | YES | **Key mental model:** a developer's local `pnpm build` can NEVER overwrite production. To populate R2 from outside CI, use the explicit `polystella translate` CLI. Configure a lifecycle rule on the R2 bucket to expire `previews/*` after 30 days. The package only prunes within its configured prefix, so cross-build cleanup of orphan preview prefixes needs the lifecycle rule. ## Override files (hand translations) Drop a file at `i18n/overrides/{locale}/<mirrored-path>` and it wins over AI output verbatim. Overrides go through the URL rewriter (idempotent) but are NOT written to R2 — they live in your repo, not in the cache. Use overrides for content you want to control exactly (legal copy, brand names, marketing taglines). ## Glossary Configure one per-locale YAML source using `glossary.file`, `glossary.http`, `glossary.r2`, or `glossary.inline`. File paths, HTTP URLs, and R2 keys use `{locale}`; PolyStella loads one object per target locale. See the glossary concept guide for the YAML shape and private repository examples. Editing a glossary changes that locale's glossary hash and re-translates the whole corpus for that locale. ## Build report After every translation pass, `astro build` (and `polystella translate`) writes `dist/i18n-r2-report.json` with per-pair outcomes: cache hits, AI translations, overrides, errors, locally-skipped pairs, prune actions. Check it when something looks wrong. ## Common pitfalls (top 10) 1. **Locale set drift** — `astro.config.mjs`'s `i18n.locales` is the only source of truth. Don't duplicate it in `polystella.config.mjs`. The `astroSitemapI18n(i18n, ...)` helper takes the same `i18n` block. Sitemap config that doesn't match Astro's `i18n` ships locale-prefixed URLs with no `hreflang` annotations — search engines treat them as duplicate content. 2. **Empty preview cache panic** — A PR preview's R2 prefix is `previews/<branch>/i18n/`, initially empty. The fallback to `i18n/` means cache hits still come from main; new translations write to the preview prefix. This is correct behaviour. 3. **Workers AI `maxTokens` default** — The schema default is `8192`. Lowering it truncates multi-segment translations into invalid JSON. Keep it at `8192` unless you've measured single-segment files and know what you're doing.
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub