Skip to content

Commit

Permalink
feat(general): documenation for functions and demo improvements
Browse files Browse the repository at this point in the history
release-npm
  • Loading branch information
tobua committed Feb 28, 2024
1 parent cc01276 commit 67ac575
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 16 deletions.
1 change: 1 addition & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
run: bun run test:node
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORGANIZATION: ${{ secrets.OPENAI_ORGANIZATION }}
# Tests running in parallel and browser environment cannot be torn down properly after tests.
- name: 🧪 Test Browser
run: bun run test:browser
Expand Down
87 changes: 78 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ AI-assisted translation library for React and React Native.
- Translations generated on demand using LLMs (AI)
- Optimized for development phase: no need to commit any translations to source!

This project was built to try the Bun 🐰 runtime, Cursor AI 🖱️ editor and CodeWhisperer 🤫.
This project was built to try the Bun 🐰 runtime, Cursor AI 🖱️ editor and CodeWhisperer 🤫. See [this blog post](https://onwebfocus.com/bun) detailing my experiences.

## Usage with Buildtime Translation

This is the most basic method where all translations are bundled into the main JavaScript bundle. This is useful for development, when supporting only few languages or when distributing as a package like React Native apps. To use this method, create a main JSON file in the default language containing the initial translations. The CLI translation script can then be used to translate these keys during installation or the build.
This is the most basic method where all translations are bundled into the main JavaScript bundle. This is useful for development, when supporting only few languages or when distributing as a package like React Native apps. To use this method, create a main JSON file in the default language containing the initial translations. The CLI translation script can then be used to translate these keys during installation or the build. Running this will require `OPENAI_API_KEY` and `OPENAI_ORGANIZATION` (optional) environment variables to be present. When using Bun with `bunx` instead of `npx` these variables will automatically be loaded from your `.env` file.

```sh
npx epic-language --input translations/en.json --output translations --language en --languages es,zh
npx epic-language --input translations.json --output translations --language en --languages es,zh
```

Once generated the translation files can be imported as JSON and bundled directly into JavaScript.
Expand Down Expand Up @@ -50,26 +50,95 @@ translate('replacement', 5) // Counter: 5
translate('multipleOrderedReplacements', ['pastime', 'your']) // What's your current pastime?
```

### Individual Translations Served As Static Files

To avoid bundling translations with your regular JavaScript code and only send the required translations to the user you can configure a static route. In most build tools assets from the `/public` folder will be served statically.

```sh
npx epic-language --input translations.json --output public/translation --language en --languages es,zh
```

```ts
import { create, Language } from 'epic-language'
// Always load the translations for the default language.
import translations from './translations.json'

const { translate } = create({
// Initial translations in default language.
translations,
// Translation files served statically as JSON files per language.
// [language] will be replaced with the language (en, es, zh etc.)
route: 'translation/[language].json',
})

translate('title') // My Title
```

## Usage with Runtime Translation and Caching

The route can point to a Serverless or Edge API function that will generate and cache the desired translations. This avoids translating every language during the build and will save a lot of time during the development phase, while ensuring translations always work.

```ts
import { create } from 'epic-language'
import translations from './translations.json'

const { translate } = create({
// Initial translations in default language.
translations: {
title: 'My Title',
description: 'This is the description.',
counter: 'Count: {}',
},
translations,
// Route to load translations for user language.
route: '/api/translations',
// [language] will be replaced with the language (en, es, zh etc.)
route: '/api/translation/[language]',
})

translate('title') // My Title
translate('counter', '5') // Counter: 5
```

The following functions have been tested on Vercel and can be adapted accordingly for other hosting providers. It's important to make sure the `OPENAI_API_KEY` and `OPENAI_ORGANIZATION` (optional) environment variables are available in your functions. Check out the [`/api` source code](https://github.com/tobua/epic-language/tree/main/demo/api) of the demo application.

### Serverless Function

```ts
import { readFileSync } from 'fs'
import { it } from 'avait'
import { Language } from 'epic-language'
import { translate } from 'epic-language/function'
import type { VercelRequest, VercelResponse } from '@vercel/node'

export default async function handler(request: VercelRequest, response: VercelResponse) {
const language = request.query.lang as Language
if (!(language in Language))
return response.status(500).json({ error: `Missing language "${language}"` })
const englishSheet = JSON.parse(
readFileSync(new URL('../../translations.json', import.meta.url), 'utf8'),
)
const { error, value: sheet } = await it(translate(JSON.stringify(englishSheet), language))
if (error)
return response.status(500).json({ error: `Translation for language "${language}" failed!` })
response.status(200).json(sheet)
}
```

### Edge Function

```ts
import { it } from 'avait'
import { Language } from 'epic-language'
import { translate } from 'epic-language/function'
import translations from '../../translations.json'

export const runtime = 'edge'

export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const language = searchParams.get('lang') as Language
if (!(language in Language)) return new Response(`Missing language "${language}"`)
const { error, value: sheet } = await it(translate(JSON.stringify(translations), language))
if (error) return new Response(`Translation for language "${language}" failed!`)
return Response.json(sheet)
}
```

## Usage with React Native

```tsx
Expand Down
2 changes: 1 addition & 1 deletion demo/api/serverless/[lang].ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default async function handler(request: VercelRequest, response: VercelRe
if (!(language in Language))
return response.status(500).json({ error: `Missing language "${language}"` })
const englishSheet = JSON.parse(
readFileSync(new URL('../../data/en.json', import.meta.url), 'utf8'),
readFileSync(new URL('../../translations.json', import.meta.url), 'utf8'),
)
const { error, value: sheet } = await it(translate(JSON.stringify(englishSheet), language))
if (error)
Expand Down
6 changes: 4 additions & 2 deletions demo/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "NodeNext",
"module": "NodeNext",
"esModuleInterop": true,
"target": "esnext",
"resolveJsonModule": true,
},
"lib": ["esnext", "dom"]
}
}
2 changes: 1 addition & 1 deletion demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const mode = sessionStorage.getItem('mode') || 'serverless'
const routeByMode = {
serverless: 'api/serverless/[language]',
edge: 'api/edge/[language]',
static: '[language].json',
static: 'translation/[language].json',
}

const baseUrl = process.env.NODE_ENV === 'production' ? '/' : 'http://localhost:3001/'
Expand Down
4 changes: 4 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
"start": "papua start",
"start:vercel": "vercel dev"
},
"papua": {
"title": "epic-language Demo",
"icon": "../logo.svg"
},
"dependencies": {
"@vercel/node": "^3.0.20",
"avait": "^1.0.0",
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"demo:files": "bun dist/bin.js --input demo/api/static/translations/en.json --output demo/api/static/translations --language en",
"server": "bun --watch server.ts",
"server:files": "bun dist/bin.js --input translations.json --output test/translation --language en",
"start": "padua watch",
"test": "bun test",
"test:browser": "bun test browser.test",
"test:node": "bun test node.test"
Expand Down Expand Up @@ -52,7 +51,7 @@
"@elysiajs/static": "^0.8.1",
"@happy-dom/global-registrator": "^13.6.2",
"@testing-library/react": "^14.2.1",
"@types/bun": "^1.0.7",
"@types/bun": "^1.0.8",
"@types/react": "^18.2.60",
"@types/react-dom": "^18.2.19",
"avait": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { readableLanguage } from './helper'

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: 'org-jec41Owv49cySXoL8adosmOh',
organization: process.env.OPENAI_ORGANIZATION,
})

export async function translate(input: string, language: Language) {
Expand Down

0 comments on commit 67ac575

Please sign in to comment.