Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
KishiTheMechanic committed Oct 26, 2024
1 parent d3ef5ac commit 87001f2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 77 deletions.
82 changes: 45 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,72 @@

`@elsoul/fresh-i18n` is a simple and flexible internationalization (i18n) plugin
for Deno's Fresh framework. It allows you to easily manage multiple languages in
your Fresh app using translations and locale detection.
your Fresh app using JSON-based translations and locale detection.

## Features

- **Easy locale detection**: Automatically detects the user's language based on
request headers.
- **JSON-based translations**: Organize your translations in JSON files, ready
for use with namespaces.
- **Simple hooks API**: Use `useTranslation()` to fetch translations and
- **Automatic Locale Detection**: Automatically detects the user's language
based on request headers or URL parameters.
- **Namespace Support**: Organize translations by namespaces in JSON files for
efficient loading and modularization.
- **Simple Hooks API**: Use `useTranslation()` to fetch translations and
`useLocale()` to get or change the current locale.
- **Edge-native**: Optimized for Deno's edge servers for high performance.
- **Dynamic language switching**: Enables dynamic language switching within your
- **Dynamic Language Switching**: Enables dynamic language switching within your
components.

## Installation

### Install via JSR

```ts
```typescript
import { i18nPlugin } from 'jsr:@elsoul/fresh-i18n'
```

### Install via Deno Land

```ts
```typescript
import { i18nPlugin } from 'https://deno.land/x/fresh_i18n/mod.ts'
```

## Usage

### Step 1: Register the Plugin
### Step 1: Register the Plugin (Updated for Fresh v2)

First, register the plugin in your `main.ts` file and provide the available
locales and JSON translations.
In your `main.ts` file, register the plugin, specifying the available locales
and default language, along with any namespaces you wish to use.

```ts
import { start } from '$fresh/server.ts'
import manifest from './fresh.gen.ts'
```typescript
import { App, fsRoutes, staticFiles, trailingSlashes } from 'fresh'
import { i18nPlugin } from '@elsoul/fresh-i18n'

await start(manifest, {
plugins: [
i18nPlugin({
locales: ['en', 'ja'],
defaultLocale: 'en',
localesDir: './locales', // Path to the directory where JSON files are stored
}),
],
export const app = new App({
root: import.meta.url,
})
.use(staticFiles()) // Serve static files
.use(trailingSlashes('never')) // Do not add trailing slashes to URLs
.use(i18nPlugin({
languages: ['en', 'ja'], // Supported languages
defaultLocale: 'en', // Default language
localesDir: './locales', // Directory path to JSON files
}, ['common', 'homepage'])) // Example of namespace customization

await fsRoutes(app, {
loadIsland: (path) => import(`./islands/${path}`),
loadRoute: (path) => import(`./routes/${path}`),
})

if (import.meta.main) {
await app.listen()
}
```

### Step 2: Create JSON Translation Files

In the `locales` directory, create locale folders and JSON translation files for
each supported locale.
In the `locales` directory, create folders for each locale and JSON files for
each namespace.

#### `locales/en/common.json`
#### Example: `locales/en/common.json`

```json
{
Expand All @@ -67,7 +76,7 @@ each supported locale.
}
```

#### `locales/ja/common.json`
#### Example: `locales/ja/common.json`

```json
{
Expand All @@ -78,21 +87,20 @@ each supported locale.

### Step 3: Use Translations in Components

Now you can use the `useTranslation()` and `useLocale()` hooks to fetch
translations and switch between locales in your components.
Use the `useTranslation()` and `useLocale()` hooks in your components to fetch
translations and switch between locales dynamically.

```tsx
import { useTranslation } from '@elsoul/fresh-i18n'
import { useLocale } from '@elsoul/fresh-i18n'
import { useLocale, useTranslation } from '@elsoul/fresh-i18n'

export default function Home() {
const { t } = useTranslation()
const { t } = useTranslation('common') // Use the "common" namespace
const { locale, changeLanguage } = useLocale()

return (
<div>
<h1>{t('common.title')}</h1> {/* 'Home' or 'ホーム' */}
<p>{t('common.welcome')}</p> {/* 'Welcome' or 'ようこそ' */}
<h1>{t('title')}</h1> {/* Outputs 'Home' or 'ホーム' */}
<p>{t('welcome')}</p> {/* Outputs 'Welcome' or 'ようこそ' */}
<p>Current language: {locale}</p>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('ja')}>日本語</button>
Expand All @@ -101,13 +109,13 @@ export default function Home() {
}
```

### Step 4: Change the Language
### Step 4: Dynamically Change Language

Use the `changeLanguage()` function from the `useLocale()` hook to dynamically
switch between languages.

```tsx
const { locale, changeLanguage } = useLocale()
const { changeLanguage } = useLocale()
changeLanguage('ja') // Switch to Japanese
```

Expand All @@ -118,5 +126,5 @@ Contributions are welcome! Please feel free to submit issues or pull requests on

## License

This package is open-sourced under the
This package is open-source and available under the
[Apache-2.0 License](https://www.apache.org/licenses/LICENSE-2.0).
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@elsoul/fresh-i18n",
"version": "0.1.0",
"version": "0.1.1",
"description": "A simple and flexible internationalization (i18n) plugin for Deno's Fresh framework.",
"runtimes": ["deno", "browser"],
"exports": "./mod.ts",
Expand Down
23 changes: 14 additions & 9 deletions src/i18nPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import type { MiddlewareFn } from './types.ts'
interface TranslationState {
locale: string
translations: { [namespace: string]: { [key: string]: string } }
loadNamespaceTranslations: (
namespace: string,
) => Promise<{ [key: string]: string }>
}

export function i18nPlugin<T>(
Expand All @@ -22,18 +25,20 @@ export function i18nPlugin<T>(
? lang
: options.defaultLanguage

// ロケールの初期化
setInitialLocale(locale)

// 名前空間ごとの翻訳データをロード
const namespaces = ['common', 'homepage']
const translations = await i18n.loadNamespaceTranslations(
locale,
namespaces,
)

ctx.state.locale = locale
ctx.state.translations = translations
ctx.state.translations = {}

ctx.state.loadNamespaceTranslations = async (namespace: string) => {
if (!ctx.state.translations[namespace]) {
const translations = await i18n.loadNamespaceTranslations(locale, [
namespace,
])
ctx.state.translations[namespace] = translations[namespace]
}
return ctx.state.translations[namespace]
}

return ctx.next()
}
Expand Down
9 changes: 3 additions & 6 deletions src/initI18n.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface I18nOptions {
languages: string[]
defaultLanguage: string
localesDir: string
}

export function initI18n(options: I18nOptions) {
Expand All @@ -12,18 +13,14 @@ export function initI18n(options: I18nOptions) {
const translations: { [namespace: string]: { [key: string]: string } } =
{}

const validLocale = options.languages.includes(locale)
? locale
: options.defaultLanguage

for (const namespace of namespaces) {
try {
const response = await fetch(
`/locales/${validLocale}/${namespace}.json`,
`${options.localesDir}/${locale}/${namespace}.json`,
)
if (!response.ok) {
throw new Error(
`Could not load translations for ${validLocale}/${namespace}`,
`Could not load translations for ${locale}/${namespace}`,
)
}
translations[namespace] = await response.json()
Expand Down
33 changes: 22 additions & 11 deletions src/useLocale.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { useSignal } from '@preact/signals'
import { createContext } from 'preact'
import { useContext } from 'preact/hooks'

const localeSignal = useSignal<string | undefined>(undefined)
interface LocaleContextProps {
locale: string
setLocale: (locale: string) => void
loadNamespaceTranslations: (
namespace: string,
) => Promise<{ [key: string]: string }>
}

export function setInitialLocale(initialLocale: string) {
if (localeSignal.value === undefined) {
localeSignal.value = initialLocale
}
const LocaleContext = createContext<LocaleContextProps>({
locale: 'en',
setLocale: () => {},
loadNamespaceTranslations: async () => ({}),
})

const localeSignal = useSignal('en')

export function setInitialLocale(locale: string) {
localeSignal.value = locale
}

export function useLocale() {
if (localeSignal.value === undefined) {
throw new Error(
'Initial locale is not set. Make sure to initialize the locale with i18nPlugin.',
)
}

const context = useContext(LocaleContext)
return {
locale: localeSignal.value,
setLocale: (newLocale: string) => {
localeSignal.value = newLocale
context.setLocale(newLocale)
},
loadNamespaceTranslations: context.loadNamespaceTranslations,
}
}
24 changes: 11 additions & 13 deletions src/useTranslation.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { useSignal } from '@preact/signals'
import { useLocale } from './useLocale.ts'
import { useEffect, useState } from 'preact/hooks'

export function useTranslation(namespace: string) {
const { locale } = useLocale()
const translations = useSignal<{ [key: string]: string }>({})
const { locale, loadNamespaceTranslations } = useLocale()
const [translations, setTranslations] = useState<Record<string, string>>({})

async function loadTranslations() {
try {
const response = await fetch(`/locales/${locale}/${namespace}.json`)
const data: { [key: string]: string } = await response.json()
translations.value = data
} catch (error) {
console.error(`Error loading translations for ${namespace}:`, error)
useEffect(() => {
async function loadTranslations() {
const namespaceTranslations = await loadNamespaceTranslations(namespace)
setTranslations(namespaceTranslations)
}
}
loadTranslations()
}, [namespace, locale])

function t(key: string): string {
return translations.value[key] || key
return translations[key] || key
}

return { t, loadTranslations }
return { t }
}

0 comments on commit 87001f2

Please sign in to comment.