Skip to content

Commit

Permalink
feat: translation utility implementation (#2)
Browse files Browse the repository at this point in the history
* feat: translation utility implementation

* updates

* update bun lock
  • Loading branch information
kazupon authored Oct 17, 2023
1 parent 95d9126 commit 2f6fc28
Show file tree
Hide file tree
Showing 9 changed files with 484 additions and 378 deletions.
15 changes: 15 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,18 @@
- name: 'status: need more repro codes or info'
description: Lacks enough info to make progress
color: 'F9C90A'
- name: '🧹 p1-chore'
description: 'Priority 1: no change in change code behavior'
color: '#FDDFD7'
- name: '🍰 p2-nice-to-have'
description: "Priority 2: nothing is broken but it's worth addressing"
color: '#0e8a16'
- name: '🔨 p3-minor-bug'
description: 'Priority 3: a bug in an edge case that only affects very specific usage'
color: '#fbca04'
- name: '❗ p4-important'
description: 'Priority 4: bugs that violate documented behavior, or significantly impact perf'
color: '#d93f0b'
- name: '🔥 p5-urgent'
description: 'Priority 5: build-breaking bugs that affect most users and should be fixed ASAP'
color: '#ee0701'
155 changes: 153 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,156 @@
# @intlify/h3

Internationalization utilitis for h3
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![CI][ci-src]][ci-href]

**NOTICE: This is a work in progress 👷**
Internationalization middleware & utilitis for h3

## 🌟 Features

✅️  **Translation:** Simple API like
[vue-i18n](https://vue-i18n.intlify.dev/)

 **Custom locale detector:** You can implement your own locale detector
on server-side

✅️️  **Useful utilities:** support internationalization composables
utilities via [@intlify/utils](https://github.com/intlify/utils)

## 💿 Installation

```sh
# Using npm
npm install @intlify/h3

# Using yarn
yarn add @intlify/h3

# Using pnpm
pnpm add @intlify/h3

# Using bun
bun add @intlify/h3
```

## 🚀 Usage

```ts
import { createServer } from 'node:http'
import { createApp, createRouter, eventHandler, toNodeListener } from 'h3'
import {
defineI18nMiddleware,
detectLocaleFromAcceptLanguageHeader,
useTranslation,
} from '@intlify/h3'

// define middleware with vue-i18n like options
const middleware = defineI18nMiddleware({
// detect locale with `accept-language` header
locale: detectLocaleFromAcceptLanguageHeader,
// resource messages
messages: {
en: {
hello: 'Hello {name}!',
},
ja: {
hello: 'こんにちは、{name}!',
},
},
// something options
// ...
})

// install middleware with `createApp` option
const app = createApp({ ...middleware })

const router = createRouter()
router.get(
'/',
eventHandler((event) => {
// use `useTranslation` in event handler
const t = useTranslation(event)
return t('hello', { name: 'h3' })
}),
)

app.use(router)
createServer(toNodeListener(app)).listen(3000)
```

## 🛠️ Custom locale detection

You can detect locale with your custom logic from current `H3Event`.

example for detecting locale from url query header:

```ts
import { defineI18nMiddleware, getQueryLocale } from '@intlify/h3'
import type { H3Event } from 'h3'

const DEFAULT_LOCALE = 'en'

// define custom locale detector
const localeDetector = (event: H3Event): string => {
try {
return getQueryLocale(event).toString()
} catch () {
return DEFAULT_LOCALE
}
}

const middleware = defineI18nMiddleware({
// set your custom locale detector
locale: localeDetector,
// something options
// ...
})
```

## 🛠️ Utilites & Helpers

`@intlify/h3` has a concept of composable utilities & helpers.

### Utilities

`@intlify/h3` composable utilities accept event (from
`eventHandler((event) => {})`) as their first argument. (Exclud
`useTranslation`) return the
[`Intl.Locale`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale)

### Translations

- `useTranslation(event)`: use translation function

### Headers

- `getHeaderLocale(event)`: get locale from `accept-language` header
- `getHeaderLocales(event)`: get some locales from `accept-language` header

### Cookies

- `getCookieLocale()`: get locale from cookie
- `setCookieLocale()`: set locale to cookie

### Misc

- `getPathLocale(event)`: get locale from path
- `getQueryLocale(event)`: get locale from query

## Helpers

- `detectLocaleFromAcceptLanguageHeader`: detect locale from `accept-language`
header

## ©️ License

[MIT](http://opensource.org/licenses/MIT)

<!-- Badges -->

[npm-version-src]: https://img.shields.io/npm/v/@intlify/h3?style=flat&colorA=18181B&colorB=FFAD33
[npm-version-href]: https://npmjs.com/package/@intlify/h3
[npm-downloads-src]: https://img.shields.io/npm/dm/@intlify/h3?style=flat&colorA=18181B&colorB=FFAD33
[npm-downloads-href]: https://npmjs.com/package/@intlify/h3
[ci-src]: https://github.com/intlify/utils/actions/workflows/ci.yml/badge.svg
[ci-href]: https://github.com/intlify/utils/actions/workflows/ci.yml
Binary file modified bun.lockb
Binary file not shown.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@
"@vitest/coverage-v8": "^0.34.4",
"bumpp": "^9.2.0",
"gh-changelogen": "^0.2.8",
"h3": "^1.8.1",
"h3": "^1.8.2",
"lint-staged": "^14.0.0",
"supertest": "^6.3.3",
"typescript": "^5.2.2",
"unbuild": "^2.0.0",
"vitest": "^0.34.4"
"vitest": "^1.0.0-beta.1"
},
"dependencies": {
"@intlify/core": "npm:@intlify/[email protected]",
"@intlify/utils": "^0.8.0"
}
}
88 changes: 88 additions & 0 deletions spec/integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { afterEach, expect, test, vi } from 'vitest'
import { createApp, eventHandler, toNodeListener } from 'h3'
import { getQueryLocale } from '@intlify/utils/h3'
import supertest from 'supertest'

import {
defineI18nMiddleware,
detectLocaleFromAcceptLanguageHeader,
useTranslation,
} from '../src/index.ts'

import type { App, H3Event } from 'h3'
import type { SuperTest, Test } from 'supertest'

let app: App
let request: SuperTest<Test>

afterEach(() => {
vi.resetAllMocks()
})

test('translation', async () => {
const middleware = defineI18nMiddleware({
locale: detectLocaleFromAcceptLanguageHeader,
messages: {
en: {
hello: 'hello, {name}',
},
ja: {
hello: 'こんにちは, {name}',
},
},
})
app = createApp({ ...middleware })
request = supertest(toNodeListener(app))

app.use(
'/',
eventHandler((event) => {
const t = useTranslation(event)
return { message: t('hello', { name: 'h3' }) }
}),
)

const res = await request.get('/').set(
'accept-language',
'en;q=0.9,ja;q=0.8',
)
expect(res.body).toEqual({ message: 'hello, h3' })
})

test('custom locale detection', async () => {
const defaultLocale = 'en'

// define custom locale detector
const localeDetector = (event: H3Event): string => {
try {
return getQueryLocale(event).toString()
} catch (_e) {
return defaultLocale
}
}

const middleware = defineI18nMiddleware({
locale: localeDetector,
messages: {
en: {
hello: 'hello, {name}',
},
ja: {
hello: 'こんにちは, {name}',
},
},
})
app = createApp({ ...middleware })
request = supertest(toNodeListener(app))

app.use(
'/',
eventHandler((event) => {
const t = useTranslation(event)
return { message: t('hello', { name: 'h3' }) }
}),
)

const res = await request.get('/?locale=ja')
expect(res.body).toEqual({ message: 'こんにちは, h3' })
})
Loading

0 comments on commit 2f6fc28

Please sign in to comment.