Skip to content

Commit

Permalink
use [locale]
Browse files Browse the repository at this point in the history
  • Loading branch information
KishiTheMechanic committed Oct 29, 2024
1 parent e6973ba commit 0a1abf3
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 14 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ export type ExtendedState = State & TranslationState
export const define = createDefine<ExtendedState>()
```

#### Create [locale] Directory on routes

Update Routing Structure to Include [locale] Folder Important: The [locale]
folder is now mandatory in your routing structure. All your route files should
be placed inside the [locale] directory to handle language prefixes in URLs
effectively.

Directory Structure Your routes directory should look like this:

```
routes/
├── [locale]/
│ ├── index.tsx
│ ├── about.tsx
│ ├── contact.tsx
│ └── ...other routes
```

### Step 2: Create Locale JSON Files

Inside the `locales` directory, create subfolders for each locale and organize
Expand Down
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": "1.0.0",
"version": "1.1.0",
"description": "A simple and flexible internationalization (i18n) plugin for Deno's Fresh framework.",
"runtimes": ["deno", "browser"],
"exports": "./mod.ts",
Expand Down
74 changes: 61 additions & 13 deletions src/i18nPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,77 @@ async function readJsonFile(filePath: string): Promise<Record<string, string>> {
}
}

/**
* Parses the Accept-Language header to determine the user's preferred language.
*
* @param acceptLanguage - The value of the Accept-Language header.
* @param supportedLanguages - Array of supported language codes.
* @param defaultLanguage - The default language code.
* @returns The matched language code or the default language.
*/
function getPreferredLanguage(
acceptLanguage: string,
supportedLanguages: string[],
defaultLanguage: string,
): string {
const languages = acceptLanguage
.split(',')
.map((lang) => {
const [code, qValue] = lang.trim().split(';q=')
return {
code: code.toLowerCase(),
q: qValue ? parseFloat(qValue) : 1.0,
}
})
.sort((a, b) => b.q - a.q)

for (const lang of languages) {
const code = lang.code.split('-')[0] // Handle regional variants like 'en-US'
if (supportedLanguages.includes(code)) {
return code
}
}

return defaultLanguage
}

/**
* Middleware function to initialize internationalization (i18n) support.
* This plugin detects the user's language based on the URL, loads the necessary
* translation files dynamically, and saves the translations, locale, and base path as
* global signals for both client-side and server-side access.
* This plugin detects the user's language based on the URL and the Accept-Language header,
* loads the necessary translation files dynamically, and saves the translations, locale,
* and base path as global signals for both client-side and server-side access.
*
* If no language code is present in the URL, it redirects to the URL with the user's preferred language.
*
* @param options - Configuration options for the i18n plugin.
* @returns A middleware function that handles language detection and translation loading.
* @returns A middleware function that handles language detection, translation loading, and redirects.
*/
export const i18nPlugin = (
{ languages, defaultLanguage, localesDir }: I18nOptions,
): MiddlewareFn<TranslationState> => {
return async (ctx) => {
const url = new URL(ctx.req.url)
const pathSegments = url.pathname.split('/').filter(Boolean)
const lang = languages.includes(pathSegments[0])
? pathSegments[0]
: defaultLanguage

const rootPath = lang === pathSegments[0]
? '/' + pathSegments.slice(1).join('/')
: url.pathname
// Detect the language from the first path segment
let lang = languages.includes(pathSegments[0]) ? pathSegments[0] : null

// If no language is detected in the URL, determine the user's preferred language
if (!lang) {
const acceptLanguage = ctx.req.headers.get('Accept-Language') || ''
lang = getPreferredLanguage(acceptLanguage, languages, defaultLanguage)

// Build the new URL by prepending the determined language
const newPathname = `/${lang}${url.pathname}`
const newUrl = new URL(url.toString())
newUrl.pathname = newPathname

// Return a 307 Temporary Redirect response
return Response.redirect(newUrl.toString(), 307)
}

// Continue processing with the detected language
const rootPath = '/' + pathSegments.slice(1).join('/')

ctx.state.path = rootPath
ctx.state.locale = lang
Expand All @@ -71,12 +120,11 @@ export const i18nPlugin = (
}
}

// Load common translations and other namespaces
await loadTranslation('common')
await loadTranslation('error')
await loadTranslation('metadata')
for (
const segment of pathSegments.slice(lang === pathSegments[0] ? 1 : 0)
) {
for (const segment of pathSegments.slice(1)) {
await loadTranslation(segment)
}

Expand Down

0 comments on commit 0a1abf3

Please sign in to comment.