@vyxos/astro-i18next
Version:
I18next integration for Astro with dynamic namespace loading.
346 lines (266 loc) • 8.92 kB
Markdown
# @vyxos/astro-i18next
# STATUS: ALPHA
High-performance i18n integration for Astro with dynamic namespace loading and TanStack Router support.
## Key Features
- **Dynamic Loading**: Load only the translation namespaces your routes need
- **Zero Bundle Bloat**: No massive translation bundles in before-hydration scripts
- **TanStack Router Integration**: Built-in helpers for route-based namespace loading
- **SSG Compatible**: Full translations available for static site generation
- **Performance First**: Optimized for minimal client-side overhead
## Installation
```bash
npm install @vyxos/astro-i18next
```
## Quick Setup
### 1. Configure Astro Integration
```typescript
// astro.config.mjs
import { defineConfig } from "astro/config";
import { i18nIntegration } from "@vyxos/astro-i18next";
export default defineConfig({
integrations: [
i18nIntegration({
translationsDir: "src/translations",
i18NextOptions: {
supportedLngs: ["en", "sk", "cs"],
lng: "en", // Optional - will use language detection if not specified
ns: ["common", "auth", "dashboard", "forms"],
defaultNS: "common",
},
}),
],
});
```
### 2. Create Translation Files
```
src/translations/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── sk/
├── common.json
├── auth.json
└── dashboard.json
```
```json
// src/translations/en/common.json
{
"welcome": "Welcome",
"loading": "Loading...",
"error": "Something went wrong"
}
```
### 3. Dynamic Loading in TanStack Router
```typescript
// src/routes/dashboard.tsx
import { createFileRoute } from "@tanstack/react-router";
import { loadNamespacesForRoute } from "@vyxos/astro-i18next/client";
import { useTranslation } from "react-i18next";
export const Route = createFileRoute("/dashboard")({
beforeLoad: async () => {
// Load only namespaces this route needs
await loadNamespacesForRoute(["common", "dashboard"]);
},
component: Dashboard,
});
function Dashboard() {
const { t } = useTranslation("dashboard");
return (
<div>
<h1>{t("title")}</h1>
<p>{t("description")}</p>
</div>
);
}
```
### 4. Nested Routes with Different Namespaces
```typescript
// src/routes/admin/users.tsx
export const Route = createFileRoute("/admin/users")({
beforeLoad: async () => {
await loadNamespacesForRoute(["common", "admin", "users", "tables"]);
},
component: UsersPage,
});
// src/routes/auth/login.tsx
export const Route = createFileRoute("/auth/login")({
beforeLoad: async () => {
await loadNamespacesForRoute(["common", "auth", "forms"]);
},
component: LoginPage,
});
```
## Advanced Usage
### Preloading for Better UX
```typescript
import {
preloadNamespaces,
loadNamespacesForRoute,
} from "@vyxos/astro-i18next/client";
export const Route = createFileRoute("/home")({
beforeLoad: async () => {
await loadNamespacesForRoute(["common", "home"]);
// Preload likely next routes in background
preloadNamespaces(["dashboard", "auth"]);
},
component: HomePage,
});
```
### Component-Level Loading
```typescript
import { useLoadNamespaces } from "@vyxos/astro-i18next/client";
import { useTranslation } from "react-i18next";
function DynamicComponent() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
useLoadNamespaces(["special-feature"]).then(() => {
setLoaded(true);
});
}, []);
if (!loaded) return <div>Loading translations...</div>;
const { t } = useTranslation("special-feature");
return <div>{t("content")}</div>;
}
```
## API Reference
### Integration Options
```typescript
interface IntegrationOptions {
translationsDir?: string; // Path to translation files (optional, defaults to "i18n")
generatedTypes?: {
dirPath?: string; // Directory for generated types (defaults to "types")
fileName?: string; // Generated types filename (defaults to "i18next-resources")
};
i18NextOptions: {
supportedLngs: string[] | false; // Available languages (false for auto-detection)
lng?: string; // Initial language (optional - use 'cimode' for debugging)
fallbackLng?: string | string[] | false; // Fallback language(s)
ns?: string[]; // Available namespaces
defaultNS?: string; // Default namespace
// ... and all other i18next InitOptions
};
}
```
### Debugging with 'cimode'
For debugging translation keys, you can set `lng: 'cimode'`:
```typescript
i18nIntegration({
i18NextOptions: {
supportedLngs: ["en", "sk", "cs"],
lng: "cimode", // Shows translation keys instead of values
ns: ["common", "auth"],
defaultNS: "common",
},
})
```
This will display keys like `common:greeting` instead of translated text, making it easy to identify missing translations or debug key usage.
### Client Functions
```typescript
// Load namespaces for current route (await in beforeLoad)
await loadNamespacesForRoute(["common", "dashboard"]);
// Load namespaces in components
const promise = useLoadNamespaces(["namespace"]);
// Fire-and-forget preloading
preloadNamespaces(["likely", "next", "routes"]);
```
## Performance Benefits
### Before (Traditional)
- ❌ All translations in before-hydration bundle
- ❌ Massive initial bundle size
- ❌ Slow hydration with unused translations
### After (Dynamic Loading)
- ✅ Only route-specific namespaces loaded
- ✅ Minimal before-hydration bundle
- ✅ Fast hydration + on-demand loading
## Development
### Setup Development Environment
```bash
git clone <repo>
cd astro-i18next
npm install
npm run dev # Watch mode
npm run build # Build package
npm run lint # Lint code
npm run typecheck # Type checking
```
### Project Structure
```
src/
├── integration.ts # Astro integration
├── scripts.ts # Generated client/server scripts
├── vite-plugin.ts # Vite virtual module plugin
├── router-integration.ts # TanStack Router helpers
├── translation-loader.ts # File system translation loading
├── config.ts # i18next configuration
├── types.ts # TypeScript definitions
├── index.ts # Server-side exports
└── client.ts # Browser-safe exports
```
### Build System
- **tsup**: Fast TypeScript bundler
- **Dual exports**: Separate client/server bundles
- **Type generation**: Full TypeScript support
- **ESLint**: Code quality and consistency
### Testing Your Changes
```bash
# In this package
npm run build
# In your Astro project
pnpm update @vyxos/astro-i18next
rm -rf node_modules/.vite .astro
pnpm run dev
```
### Publishing
```bash
npm version patch|minor|major
npm publish
```
## Architecture
### Translation Loading Flow
1. **Build Time**: Integration discovers all translation files
2. **SSG**: Full translations embedded in server script (for static generation)
3. **Client**: Empty namespace list, dynamic backend for on-demand loading
4. **Route Navigation**: `beforeLoad` calls `loadNamespacesForRoute()`
5. **Dynamic Import**: Vite resolves virtual modules to actual translations
6. **Caching**: i18next caches loaded namespaces in memory
### Virtual Module System
- `virtual:i18n-loader` → Dynamic translation loader
- `virtual:i18n-translation:sk/common` → Specific translation file
- `./virtual-i18n-sk-common.js` → Vite-compatible relative imports
## Future Roadmap
- [ ] **Testing Suite**: Unit tests with Vitest
- [ ] **Integration Tests**: E2E testing with Playwright
- [ ] **Language Detection**: Enhanced browser language detection
- [ ] **Hot Reloading**: Dev server translation updates
- [ ] **Namespace Splitting**: Automatic route analysis
- [ ] **Bundle Analysis**: Translation usage reporting
- [ ] **CDN Support**: External translation loading
- [ ] **Caching Strategy**: Advanced client-side caching
- [ ] **SSR Optimization**: Server-side translation hydration
- [ ] **Astro DB Integration**: Database-driven translations
- [ ] **Translation Management**: UI for managing translations
- [ ] **Plural Rules**: Advanced pluralization support
- [ ] **Context Support**: Translation context handling
- [ ] **Performance Metrics**: Loading time analytics
## Troubleshooting
### Dynamic Import Errors
Ensure you're using the `/client` import for TanStack Router:
```typescript
import { loadNamespacesForRoute } from "@vyxos/astro-i18next/client";
```
### Cache Issues
Clear all caches when updating:
```bash
rm -rf node_modules/.vite .astro dist
```
### Build Failures
Check that translation files exist in the expected structure and contain valid JSON.
## Contributing
1. Fork the repository
2. Create feature branch: `git checkout -b feature/amazing-feature`
3. Make changes and add tests
4. Run linting: `npm run lint`
5. Submit pull request
## License
MIT © [Marek Fodor](https://github.com/vyxos)