diff --git a/README.md b/README.md index 57bb6697..25bd6821 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ ## Elysia documentation + Written by VitePress + +### How to add translations + +#### Adding a net-new language + +To add a net-new language: + +1. Add the locale to the exported object in `/intl/locales.ts`. Use other languages as examples for what you need to add. +2. The new language object should have a `label` (the native name of the language), `lang` (the ISO language code), and a `themeConfig` where you pass the language code to the `generateThemeConfig` helper function. + +By default this new language will have all English content as a fallback. Follow the steps below to translate docs or the navigation. + +#### To the navigation/sidebar + +1. In `/menus` find the `text` string you want to translate in either `nav.ts` or `sidebar.ts` +2. Copy that string exactly as it appears in that object. +3. In `/intl/strings.ts` CMD+F for the string you want to translate. If it does not exist create a new object. +4. Add the lang code to the object with your translation. +5. Run the site locally to confirm your changes in the new language as well as checking nothing changed in the 'en' version. + +#### Translating docs + +All the files and directories in `/docs` represent the `en` content, with the exception of any directory named after a locale (for example `/fr` for French). Any directory named after a locale contains docs specifically translated for that locale. If a doc doesn't exist for a specific locale, redirects are set up to serve fallback content. + +Given the above, if a doc doesn't exist in the locale's directory: + +1. Copy the doc +2. Create a file with the same name in the locale's directory +3. Paste the copied content and translate. diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8f465e02..c543d4a3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,4 +1,5 @@ import { defineConfig } from 'vitepress' +import locales from '../../intl/locales' const description = 'Ergonomic Framework for Humans. TypeScript framework supercharged by Bun with End - to - End Type Safety, unified type system and outstanding developer experience' @@ -9,6 +10,7 @@ export default defineConfig({ // description, ignoreDeadLinks: true, lastUpdated: true, + locales: locales, markdown: { theme: { light: 'github-light', @@ -101,307 +103,6 @@ export default defineConfig({ provider: 'local' }, logo: '/assets/elysia.svg', - nav: [ - { - text: 'Quick Start', - link: '/quick-start' - }, - { - text: 'Plugins', - link: '/plugins/overview' - }, - { - text: 'Blog', - link: '/blog' - } - ], - sidebar: [ - { - text: '👋 Getting Started', - items: [ - { - text: 'Introduction', - link: '/introduction' - }, - { - text: 'Quick Start', - link: '/quick-start' - }, - { - text: 'At Glance', - link: '/at-glance' - }, - { - text: 'Table of Content', - link: '/table-of-content' - } - ] - }, - { - text: '✨ Essential', - collapsed: true, - items: [ - { - text: 'Route', - link: '/essential/route' - }, - { - text: 'Path', - link: '/essential/path' - }, - { - text: 'Handler', - link: '/essential/handler' - }, - { - text: 'Context', - link: '/essential/context' - }, - { - text: 'Plugin', - link: '/essential/plugin' - }, - { - text: 'Life Cycle', - link: '/essential/life-cycle' - }, - { - text: 'Schema', - link: '/essential/schema' - }, - { - text: 'Scope', - link: '/essential/scope' - } - ] - }, - { - text: '🔎 Validation', - collapsed: true, - items: [ - { - text: 'Overview', - link: '/validation/overview' - }, - { - text: 'Schema Type', - link: '/validation/schema-type' - }, - { - text: 'Primitive Type', - link: '/validation/primitive-type' - }, - { - text: 'Elysia Type', - link: '/validation/elysia-type' - }, - { - text: 'Error Provider', - link: '/validation/error-provider' - }, - { - text: 'Reference Model', - link: '/validation/reference-model' - } - ] - }, - { - text: '⏳ Life Cycle', - collapsed: true, - items: [ - { - text: 'Overview', - link: '/life-cycle/overview' - }, - { - text: 'On Request', - link: '/life-cycle/request' - }, - { - text: 'Parse', - link: '/life-cycle/parse' - }, - { - text: 'Transform', - link: '/life-cycle/transform' - }, - { - text: 'Before Handle', - link: '/life-cycle/before-handle' - }, - { - text: 'After Handle', - link: '/life-cycle/after-handle' - }, - { - text: 'Map Response', - link: '/life-cycle/map-response' - }, - { - text: 'On Error', - link: '/life-cycle/on-error' - }, - { - text: 'On Response', - link: '/life-cycle/on-response' - }, - { - text: 'Trace', - link: '/life-cycle/trace' - } - ] - }, - { - text: '🧭 Patterns', - collapsed: true, - items: [ - { - text: 'Group', - link: '/patterns/group' - }, - { - text: 'Cookie', - link: '/patterns/cookie' - }, - { - text: 'Cookie Signature', - link: '/patterns/cookie-signature' - }, - { - text: 'Web Socket', - link: '/patterns/websocket' - }, - { - text: 'Documentation', - link: '/patterns/documentation' - }, - { - text: 'Unit Test', - link: '/patterns/unit-test' - }, - { - text: 'Mount', - link: '/patterns/mount' - }, - { - text: 'Lazy Loading Module', - link: '/patterns/lazy-loading-module' - }, - { - text: 'Macro', - link: '/patterns/macro' - } - ] - }, - { - text: '🪴 Eden', - collapsed: true, - items: [ - { - text: 'Overview', - link: '/eden/overview.md' - }, - { - text: 'Installation', - link: '/eden/installation.md' - }, - { - text: 'Eden Treaty', - link: '/eden/treaty.md' - }, - { - text: 'Eden Fetch', - link: '/eden/fetch.md' - }, - { - text: 'Test', - link: '/eden/test.md' - } - ] - }, - { - text: '🔌 Plugins', - items: [ - { - text: 'Official Plugins', - link: '/plugins/overview', - collapsed: true, - items: [ - { - text: 'Bearer', - link: '/plugins/bearer' - }, - { - text: 'CORS', - link: '/plugins/cors' - }, - { - text: 'Cron', - link: '/plugins/cron' - }, - { - text: 'GraphQL Apollo', - link: '/plugins/graphql-apollo' - }, - { - text: 'GraphQL Yoga', - link: '/plugins/graphql-yoga' - }, - { - text: 'HTML', - link: '/plugins/html' - }, - { - text: 'JWT', - link: '/plugins/jwt' - }, - { - text: 'Server Timing', - link: '/plugins/server-timing' - }, - { - text: 'Static', - link: '/plugins/static' - }, - { - text: 'Stream', - link: '/plugins/stream' - }, - { - text: 'Swagger', - link: '/plugins/swagger' - }, - { - text: 'trpc', - link: '/plugins/trpc' - } - ] - } - ] - }, - { - text: 'Integration', - collapsed: true, - items: [ - { - text: 'Docker', - link: '/integrations/docker' - }, - { - text: 'Nextjs', - link: '/integrations/nextjs' - }, - { - text: 'Astro', - link: '/integrations/astro' - } - // { - // text: 'Cheat Sheet', - // link: '/integrations/cheat-sheet' - // } - ] - } - ], socialLinks: [ { icon: 'github', link: 'https://github.com/elysiajs/elysia' }, { icon: 'twitter', link: 'https://twitter.com/elysiajs' }, diff --git a/docs/fr/quick-start.md b/docs/fr/quick-start.md new file mode 100644 index 00000000..5de41aa2 --- /dev/null +++ b/docs/fr/quick-start.md @@ -0,0 +1,111 @@ +--- +title: Quick Start - ElysiaJS +head: + - - meta + - property: 'og:title' + content: Quick Start - ElysiaJS + + - - meta + - name: 'description' + content: Elysia is a library built for Bun and the only prerequisite. To start, boostrap a new project with "bun create elysia hi-elysia" and start development server with "bun dev". This is all it need to do a quick start or getting start with ElysiaJS. + + - - meta + - property: 'og:description' + content: Elysia is a library built for Bun and the only prerequisite. To start, boostrap a new project with "bun create elysia hi-elysia" and start development server with "bun dev". This is all it need to do a quick start or getting start with ElysiaJS. +--- + +# Démarrage rapide + +System Requirements: + +- [Bun](https://bun.sh) +- MacOS, Windows (including WSL), and Linux are supported. +- TypeScript > 5.0 (for language server) + +## Bun + +Elysia is optimized for Bun which is a JavaScript runtime aims to be drop-in replacement for Node.js. + +You can install Bun with the command below: + +```bash +curl https://bun.sh/install | bash +``` + +## Automatic Installation + +We recommend starting a new Elysia server using `bun create elysia`, which sets up everything automatically for you. + +To create a project, run: + +```bash +bun create elysia app +``` + +Once done, you should see the folder name `app` in your directory. + +```bash +cd app +``` + +Start a development server by: + +```bash +bun dev +``` + +Navigate to [localhost:3000](http://localhost:3000) should greet you with "Hello Elysia". + +::: tip +Elysia ships you with `dev` command to automatically reload your server on file change. +::: + +## Manual Installation + +To manually create a new Next.js app, install Elysia as a package: + +```typescript +bun add elysia +``` + +Open your `package.json` file and add the following scripts: + +```json +{ + "scripts": { + "dev": "bun --watch src/index.ts", + "build": "bun build src/index.ts", + "start": "NODE_ENV=production bun src/index.ts", + "test": "bun test" + } +} +``` + +These scripts refer to the different stages of developing an application: + +- **dev** - Start Elysia in development mode with auto reload on code change. +- **build** - Build the application for production usage. +- **start** - Start an Elysia production server. + +If you are using TypeScript, make sure to create, and update `tsconfig.json` to include `compilerOptions.strict` to `true`: + +```json +{ + "compilerOptions": { + "strict": true + } +} +``` + +## Structure + +Here's the recommended file structure for Elysia if you don't strictly prefers a specific convention: + +- **src** - Any file that associate with development of Elysia server. + - **index.ts** - Entry point for your Elysia server, ideal place for setting global plugin + - **setup.ts** - Composed of various plugins to be used as a Service Locator + - **controllers** - Instance which encapsulate multiple endpoints + - **libs** - Utility functions + - **models** - Data Type Objects (DTOs) for Elysia instance + - **types** - Shared TypeScript type if needed +- **test** - Test file for Elysia server diff --git a/intl/helpers.ts b/intl/helpers.ts new file mode 100644 index 00000000..518b6327 --- /dev/null +++ b/intl/helpers.ts @@ -0,0 +1,46 @@ +import { AvailableLocales } from './locales' +import strings from './strings' + +function getTranslation(key: string, lang: AvailableLocales) { + // Use the key as the fallback if nothing exists in translations.json + const anyTranslation = strings.hasOwnProperty(key) + if (!anyTranslation) { + return key + } + // Do we have the exact translation? + const thisTranslation = anyTranslation && strings[key].hasOwnProperty(lang) + if (thisTranslation) { + return strings[key][lang] + } + // Do we have a fallback? + // Failsafe if something is truly whack + return key +} +export function replaceLocalizedContent( + passedObj: any, + lang: AvailableLocales +) { + const obj = passedObj + // Check if the input is an object + if (typeof obj === 'object' && obj !== null) { + // Iterate over the object properties + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + // Check if the current property is named "text" + if (key === 'text' || key === 'link') { + if (key === 'text') { + // Look for translated strings + obj[key] = getTranslation(obj[key], lang) + } + if (key === 'link' && lang !== 'en') { + obj[key] = `${lang}${obj[key]}` + } + } else { + // If the current property is not "text", recursively process its value + replaceLocalizedContent(obj[key], lang) + } + } + } + } + return obj +} diff --git a/intl/locales.ts b/intl/locales.ts new file mode 100644 index 00000000..91b3f205 --- /dev/null +++ b/intl/locales.ts @@ -0,0 +1,30 @@ +/** + * Generates the locales object used in /docs/.vitepress/config.ts + */ +import { nav, sidebar } from '../menus' +import { replaceLocalizedContent } from './helpers' + +export type AvailableLocales = 'en' | 'fr' + +function generateThemeConfig(lang: AvailableLocales) { + return { + nav: replaceLocalizedContent(nav, lang), + sidebar: replaceLocalizedContent(sidebar, lang) + } +} + +export default { + root: { + label: 'English', + lang: 'en', + themeConfig: { + nav, + sidebar + } + }, + fr: { + label: 'French', + lang: 'fr', + themeConfig: generateThemeConfig('fr') + } +} diff --git a/intl/strings.ts b/intl/strings.ts new file mode 100644 index 00000000..1f6cabdc --- /dev/null +++ b/intl/strings.ts @@ -0,0 +1,14 @@ +/** + * Home for translating strings in the menus + */ +export default { + 'Get started': { + en: 'Get started', + fr: 'Commencer' + }, + + 'Quick Start': { + en: 'Quick start', + fr: 'Démarrage rapide' + } +} diff --git a/menus/index.ts b/menus/index.ts new file mode 100644 index 00000000..04c0e476 --- /dev/null +++ b/menus/index.ts @@ -0,0 +1,8 @@ +/** + * Barrel export, check /intl/locales.ts to see where these end up. + * To change translations of navigation items, check /intl/strings.ts + */ +import nav from './nav' +import sidebar from './sidebar' + +export { nav, sidebar } diff --git a/menus/nav.ts b/menus/nav.ts new file mode 100644 index 00000000..ee1fef62 --- /dev/null +++ b/menus/nav.ts @@ -0,0 +1,14 @@ +export default [ + { + text: 'Quick Start', + link: '/quick-start' + }, + { + text: 'Plugins', + link: '/plugins/overview' + }, + { + text: 'Blog', + link: '/blog' + } +] diff --git a/menus/sidebar.ts b/menus/sidebar.ts new file mode 100644 index 00000000..9f5a1d21 --- /dev/null +++ b/menus/sidebar.ts @@ -0,0 +1,287 @@ +export default [ + { + text: '👋 Getting Started', + items: [ + { + text: 'Introduction', + link: '/introduction' + }, + { + text: 'Quick Start', + link: '/quick-start' + }, + { + text: 'At Glance', + link: '/at-glance' + }, + { + text: 'Table of Content', + link: '/table-of-content' + } + ] + }, + { + text: '✨ Essential', + collapsed: true, + items: [ + { + text: 'Route', + link: '/essential/route' + }, + { + text: 'Path', + link: '/essential/path' + }, + { + text: 'Handler', + link: '/essential/handler' + }, + { + text: 'Context', + link: '/essential/context' + }, + { + text: 'Plugin', + link: '/essential/plugin' + }, + { + text: 'Life Cycle', + link: '/essential/life-cycle' + }, + { + text: 'Schema', + link: '/essential/schema' + }, + { + text: 'Scope', + link: '/essential/scope' + } + ] + }, + { + text: '🔎 Validation', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/validation/overview' + }, + { + text: 'Schema Type', + link: '/validation/schema-type' + }, + { + text: 'Primitive Type', + link: '/validation/primitive-type' + }, + { + text: 'Elysia Type', + link: '/validation/elysia-type' + }, + { + text: 'Error Provider', + link: '/validation/error-provider' + }, + { + text: 'Reference Model', + link: '/validation/reference-model' + } + ] + }, + { + text: '⏳ Life Cycle', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/life-cycle/overview' + }, + { + text: 'On Request', + link: '/life-cycle/request' + }, + { + text: 'Parse', + link: '/life-cycle/parse' + }, + { + text: 'Transform', + link: '/life-cycle/transform' + }, + { + text: 'Before Handle', + link: '/life-cycle/before-handle' + }, + { + text: 'After Handle', + link: '/life-cycle/after-handle' + }, + { + text: 'Map Response', + link: '/life-cycle/map-response' + }, + { + text: 'On Error', + link: '/life-cycle/on-error' + }, + { + text: 'On Response', + link: '/life-cycle/on-response' + }, + { + text: 'Trace', + link: '/life-cycle/trace' + } + ] + }, + { + text: '🧭 Patterns', + collapsed: true, + items: [ + { + text: 'Group', + link: '/patterns/group' + }, + { + text: 'Cookie', + link: '/patterns/cookie' + }, + { + text: 'Cookie Signature', + link: '/patterns/cookie-signature' + }, + { + text: 'Web Socket', + link: '/patterns/websocket' + }, + { + text: 'Documentation', + link: '/patterns/documentation' + }, + { + text: 'Unit Test', + link: '/patterns/unit-test' + }, + { + text: 'Mount', + link: '/patterns/mount' + }, + { + text: 'Lazy Loading Module', + link: '/patterns/lazy-loading-module' + }, + { + text: 'Macro', + link: '/patterns/macro' + } + ] + }, + { + text: '🪴 Eden', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/eden/overview.md' + }, + { + text: 'Installation', + link: '/eden/installation.md' + }, + { + text: 'Eden Treaty', + link: '/eden/treaty.md' + }, + { + text: 'Eden Fetch', + link: '/eden/fetch.md' + }, + { + text: 'Test', + link: '/eden/test.md' + } + ] + }, + { + text: '🔌 Plugins', + items: [ + { + text: 'Official Plugins', + link: '/plugins/overview', + collapsed: true, + items: [ + { + text: 'Bearer', + link: '/plugins/bearer' + }, + { + text: 'CORS', + link: '/plugins/cors' + }, + { + text: 'Cron', + link: '/plugins/cron' + }, + { + text: 'GraphQL Apollo', + link: '/plugins/graphql-apollo' + }, + { + text: 'GraphQL Yoga', + link: '/plugins/graphql-yoga' + }, + { + text: 'HTML', + link: '/plugins/html' + }, + { + text: 'JWT', + link: '/plugins/jwt' + }, + { + text: 'Server Timing', + link: '/plugins/server-timing' + }, + { + text: 'Static', + link: '/plugins/static' + }, + { + text: 'Stream', + link: '/plugins/stream' + }, + { + text: 'Swagger', + link: '/plugins/swagger' + }, + { + text: 'trpc', + link: '/plugins/trpc' + } + ] + } + ] + }, + { + text: 'Integration', + collapsed: true, + items: [ + { + text: 'Docker', + link: '/integrations/docker' + }, + { + text: 'Nextjs', + link: '/integrations/nextjs' + }, + { + text: 'Astro', + link: '/integrations/astro' + } + // { + // text: 'Cheat Sheet', + // link: '/integrations/cheat-sheet' + // } + ] + } +] diff --git a/package.json b/package.json index 810e1730..8edf8caa 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,42 @@ { - "name": "elysia-docs", - "version": "1.0.0", - "description": "", - "type": "module", - "main": "index.js", - "keywords": [], - "author": "", - "license": "MIT", - "dependencies": { - "@iconify/json": "^2.2.145", - "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", - "@tailwindcss/postcss7-compat": "^2.2.17", - "autoprefixer": "^10.4.16", - "daisyui": "^4.4.2", - "elysia": "^0.7.29", - "openai": "^4.19.1", - "postcss": "^8.4.31", - "postcss-nesting": "^12.0.1", - "postcss-preset-env": "^9.3.0", - "prismjs": "^1.29.0", - "tailwindcss": "^3.3.5", - "vite": "^5.0.2", - "vite-plugin-icons": "^0.6.5", - "vue-prism-component": "^2.0.0", - "windicss": "^3.5.6" - }, - "devDependencies": { - "@types/node": "^20.9.4", - "bun-types": "^1.0.13", - "cheerio": "1.0.0-rc.12", - "flexsearch": "^0.7.31", - "markdown-it": "^13.0.2", - "vitepress": "1.0.0-rc.29", - "vue": "^3.3.8" - }, - "scripts": { - "dev": "vitepress dev docs", - "build": "vitepress build docs", - "serve": "vitepress serve docs" - } + "name": "elysia-docs", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.js", + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@iconify/json": "^2.2.145", + "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", + "@tailwindcss/postcss7-compat": "^2.2.17", + "autoprefixer": "^10.4.16", + "daisyui": "^4.4.2", + "elysia": "^0.7.29", + "openai": "^4.19.1", + "postcss": "^8.4.31", + "postcss-nesting": "^12.0.1", + "postcss-preset-env": "^9.3.0", + "prismjs": "^1.29.0", + "tailwindcss": "^3.3.5", + "vite": "^5.0.2", + "vite-plugin-icons": "^0.6.5", + "vue-prism-component": "^2.0.0", + "windicss": "^3.5.6" + }, + "devDependencies": { + "@types/node": "^20.9.4", + "bun-types": "^1.0.13", + "cheerio": "1.0.0-rc.12", + "flexsearch": "^0.7.31", + "markdown-it": "^13.0.2", + "vitepress": "1.0.0-rc.29", + "vue": "^3.3.8" + }, + "scripts": { + "dev": "vitepress dev docs", + "build": "vitepress build docs", + "serve": "vitepress serve docs" + } } diff --git a/tsconfig.json b/tsconfig.json index b9a58dc7..0337b785 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,14 @@ { - "compilerOptions": { - "lib": ["ES2022", "DOM.Iterable", "DOM"], - "module": "esnext", - "target": "esnext", - "moduleResolution": "node", + "compilerOptions": { + "lib": ["ES2022", "DOM.Iterable", "DOM"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", - // so that if your project isn't using TypeScript, it still has autocomplete - "allowJs": true, + // so that if your project isn't using TypeScript, it still has autocomplete + "allowJs": true, - // "bun-types" is the important part - "types": ["bun-types"] - } + // "bun-types" is the important part + "types": ["bun-types"] + } }