一键导入
astro-i18n
Astro internationalization with built-in i18n routing, locale detection, and cookie-based language persistence
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Astro internationalization with built-in i18n routing, locale detection, and cookie-based language persistence
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | astro-i18n |
| description | Astro internationalization with built-in i18n routing, locale detection, and cookie-based language persistence |
| license | MIT |
| compatibility | opencode |
Prerequisites: Astro v6+ with built-in i18n routing
i18n: {
locales: ['pt-br', 'en'],
defaultLocale: 'pt-br',
routing: {
prefixDefaultLocale: false,
}
}
src/i18n/ui.ts # Translation strings
src/i18n/utils.ts # Helper functions
src/pages/
├── index.astro # default locale (pt-br)
├── about.astro # default locale (pt-br)
├── contact.astro # default locale (pt-br)
├── blog/index.astro # default locale (pt-br)
└── en/
├── index.astro # English
├── about.astro # English
└── ...
| prefixDefaultLocale | default locale URL | other locale URL |
|---|---|---|
| false | / | /en/ |
| true | /pt-br/ | /en/ |
export const languages = {
en: "English",
"pt-br": "Português (Brasil)",
} as const;
export const defaultLang = "pt-br";
export const ui = {
en: {
"nav.home": "Home",
"nav.blog": "Blog",
"nav.about": "About",
"nav.contact": "Contact",
"footer.copyright": "All rights reserved.",
},
"pt-br": {
"nav.home": "Início",
"nav.blog": "Blog",
"nav.about": "Sobre",
"nav.contact": "Contato",
"footer.copyright": "Todos os direitos reservados.",
},
} as const;
import { ui, defaultLang } from "./ui";
export function getLangFromUrl(url: URL): keyof typeof ui {
const segments = url.pathname.split("/").filter(Boolean);
const firstSegment = segments[0];
if (firstSegment && firstSegment in ui) {
return firstSegment as keyof typeof ui;
}
return defaultLang;
}
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
return ui[lang][key] ?? ui[defaultLang][key];
};
}
---
import { getRelativeLocaleUrl } from 'astro:i18n';
import { getLangFromUrl, useTranslations } from '../i18n/utils';
import HeaderLink from './HeaderLink.astro';
import LanguagePicker from './LanguagePicker.astro';
import Logo from './Logo.astro';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<header>
<nav>
<Logo size="md" showTitle />
<div>
<HeaderLink href={getRelativeLocaleUrl(lang, '/')}>
{t('nav.home')}
</HeaderLink>
<HeaderLink href={getRelativeLocaleUrl(lang, '/blog')}>
{t('nav.blog')}
</HeaderLink>
<HeaderLink href={getRelativeLocaleUrl(lang, '/about')}>
{t('nav.about')}
</HeaderLink>
</div>
<LanguagePicker />
</nav>
</header>
---
import { getRelativeLocaleUrl } from 'astro:i18n';
import { languages } from '../i18n/ui';
import { getLangFromUrl } from '../i18n/utils';
const currentLang = getLangFromUrl(Astro.url);
const pathname = Astro.url.pathname;
function getPathWithoutLocale(path, lang) {
const prefix = `/${lang}`;
if (path.startsWith(prefix)) {
return path.slice(prefix.length) || '/';
}
return path;
}
const currentPath = getPathWithoutLocale(pathname, currentLang);
---
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-ghost btn-sm" aria-label="Select language">
{languages[currentLang]}
</button>
<ul class="dropdown-content menu z-50 mt-2 w-52 rounded-box bg-base-200 p-2 shadow">
{Object.entries(languages).map(([lang, label]) => (
<li>
<a
href={getRelativeLocaleUrl(lang, currentPath)}
class:list={{ active: lang === currentLang }}
data-lang={lang}
>
{label}
</a>
</li>
))}
</ul>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-lang]').forEach((link) => {
link.addEventListener('click', (e) => {
const lang = (e.target as HTMLElement).dataset.lang;
if (lang) {
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
document.cookie = `lang=${lang};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
}
});
});
});
</script>
ALL user-visible text in .astro files MUST use the i18n system. Hardcoded
strings in templates are forbidden.
---
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<HeroSection title={t('about.hero.title')} eyebrow={t('about.hero.eyebrow')} />
<SectionHeader title={t('contact.methods.title')} subtitle={t('contact.methods.subtitle')} />
<p>{t('contributing.donations.body')}</p>
<HeroSection title="Sobre nós" eyebrow="PodCodar" />
<SectionHeader title="Canais de Comunicação" subtitle="Escolha o canal..." />
<p>Se quiser contribuir financeiramente...</p>
Data-driven content from src/data/*.ts files (board member names, metric
values, channel names, project names) is iterable data, not display strings.
These may be used directly:
<!-- OK — iterated from marketing.ts data -->
{projects.map((project) => <h3>{project.name}</h3>)}
<!-- NOT OK — hardcoded section header -->
<h2>Projetos e repositórios</h2>
<!-- Should be: -->
<h2>{t('about.projects.title')}</h2>
When adding a new key to src/i18n/ui.ts, follow the namespace pattern:
{page}.{section}.{field}
Examples:
about.hero.eyebrow — About page, hero section, eyebrow badgejoinUs.steps.01.title — Join Us page, steps section, step 1 titlecontributing.volunteering.body — Contributing page, volunteering section, body textWRONG:
<html lang="en"></html>
CORRECT:
---
import { getLangFromUrl } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
---
<html lang={lang}>
WRONG:
<a href="/about"></a>
CORRECT:
---
import { getRelativeLocaleUrl } from 'astro:i18n';
const lang = getLangFromUrl(Astro.url);
---
<a href={getRelativeLocaleUrl(lang, '/about')}>
WRONG (default must be explicitly set):
locales: ['en', 'pt-br'],
defaultLocale: 'en',
CORRECT:
locales: ['pt-br', 'en'],
defaultLocale: 'pt-br',
/) has correct lang attribute/en/ has correct lang attributegetRelativeLocaleUrl()✓ /index.html # pt-br (default)
✓ /about/index.html # pt-br
✓ /contact/index.html # pt-br
✓ /blog/index.html # pt-br
✓ /en/index.html # en
✓ /en/about/index.html # en
✓ /en/contact/index.html
✓ /en/blog/index.html # en
For server-side redirect based on browser language, add SSR adapter:
@astrojs/nodeoutput: 'server'src/middleware.ts for server-side detectionFor static sites, client-side cookie persistence works well.
src/
├── i18n/
│ ├── ui.ts
│ └── utils.ts
├── pages/
│ ├── index.astro # pt-br (default)
│ ├── about.astro # pt-br (default)
│ ├── contact.astro # pt-br (default)
│ ├── blog/
│ │ └── index.astro
│ └── en/
│ ├── index.astro
│ ├── about.astro
│ ├── contact.astro
│ └── blog/
│ └── index.astro
├── components/
│ ├── Header.astro
│ ├── Footer.astro
│ └── LanguagePicker.astro
└── layouts/
└── BlogPost.astro
Facilitate structured brainstorming sessions to generate, explore, and refine ideas before committing to requirements. Use when the user has a vague concept, wants to explore possibilities, needs creative solutions, or is in the early ideation phase. Do NOT use when the requirements are already clear and the user is ready to plan implementation (use create-prd or prd-to-tasks instead).
Interview the human-in-the-loop to clarify ambiguous requests before taking action. Use when the user's request is vague, missing key details, or has multiple valid interpretations. Do NOT use when the request is clear and unambiguous, or when the clarification can be resolved by reading code, docs, or a quick web search.
Read a tasks.json file, resolve the dependency graph, and delegate each task to specialized expert agents using the Mixture of Experts (MoE) orchestration pattern. Use when a tasks.json exists and the user wants to start implementing, or says "implement the tasks," "build it," or "execute the plan." Do NOT use when no tasks.json exists (run prd-to-tasks first), for a single straightforward task (just do it directly), or when the user wants manual control over each step.
Convert a Product Requirements Document (PRD) into a structured tasks.json file with tasks, dependencies, priorities, and estimated effort. Use when a PRD exists and the user wants to break it down into implementable tasks, or says "turn this into tasks," "create issues from the PRD," or "what are the next steps?" Do NOT use when there is no PRD or when the user wants to skip planning and start coding immediately.
Create a Product Requirements Document (PRD) from recent discussions, conversations, or accumulated context. Use when the user wants to formalize requirements, document what was discussed, or create a spec to guide implementation. Do NOT use for technical implementation specs (use SPEC.md from project-files) or for already-implemented features (use CHANGELOG.md).
Research and explore codebases to build context before making changes. Use when starting work on an unfamiliar project, investigating a bug, planning a feature, or when you need to understand how something works.