elysia-intlayer 9.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -0
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +28 -0
- package/dist/cjs/index.cjs +149 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/index.mjs +145 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/types/index.d.ts +107 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/intlayer/dist/types/index.d.ts +14 -0
- package/package.json +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://intlayer.org" rel="">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/aymericzip/intlayer/main/docs/assets/cover.png" width="60%" alt="Intlayer Logo" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<h1 align="center">
|
|
8
|
+
<strong>Per-component i18n</strong>
|
|
9
|
+
</h1>
|
|
10
|
+
|
|
11
|
+
<h2 align="center">
|
|
12
|
+
<strong>AI-powered translation. Visual Editor. Multilingual CMS.</strong>
|
|
13
|
+
</h2>
|
|
14
|
+
|
|
15
|
+
<br />
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://intlayer.org/doc/concept/content" rel="">Docs</a> •
|
|
19
|
+
<a href="https://intlayer.org/doc/environment/nextjs" rel="">Next.js</a> •
|
|
20
|
+
<a href="https://intlayer.org/doc/environment/vite-and-react" rel="">React + Vite</a> •
|
|
21
|
+
<a href="https://intlayer.org/doc/concept/cms" rel="">CMS</a> •
|
|
22
|
+
<a href="https://discord.gg/7uxamYVeCk" rel="noopener noreferrer nofollow">Discord</a>
|
|
23
|
+
</p>
|
|
24
|
+
<p align="center" style="margin-top:15px;">
|
|
25
|
+
<a href="https://www.npmjs.com/package/intlayer" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/npm/v/intlayer?style=for-the-badge&labelColor=FFFFFF&color=000000&logoColor=FFFFFF" alt="npm version" height="24"/></a>
|
|
26
|
+
<a href="https://github.com/aymericzip/intlayer/stargazers" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/stars/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logo=github&logoColor=FFD700" alt="GitHub Stars" height="24"/></a>
|
|
27
|
+
<a href="https://www.npmjs.org/package/intlayer" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/npm/dm/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="monthly downloads" height="24"/></a>
|
|
28
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/LICENSE" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/license/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="license"/></a>
|
|
29
|
+
<a href="https://github.com/aymericzip/intlayer/commits/main" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/last-commit/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="last commit"/>
|
|
30
|
+
</a>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
<a href="https://intlayer.org/doc/concept/content" rel="">
|
|
36
|
+
<img src="https://img.shields.io/badge/Get_Started-FFFFFF?style=for-the-badge&logo=rocket&logoColor=black" />
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
## What is Intlayer?
|
|
40
|
+
|
|
41
|
+
Most i18n libraries are either too complex, too rigid, or not built for modern frameworks.
|
|
42
|
+
|
|
43
|
+
Intlayer is a **modern i18n solution** for web and mobile apps.
|
|
44
|
+
It’s framework-agnostic, **AI-powered**, and includes a free **CMS & visual editor**.
|
|
45
|
+
|
|
46
|
+
With **per-locale content files**, **TypeScript autocompletion**, **tree-shakable dictionaries**, and **CI/CD integration**, Intlayer makes internationalization **faster, cleaner, and smarter**.
|
|
47
|
+
|
|
48
|
+
## Keys benefits of Intlayer:
|
|
49
|
+
|
|
50
|
+
| Feature | Description |
|
|
51
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true" alt="Feature" width="700"> | **Cross-Frameworks Support**<br><br>Intlayer is compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more. |
|
|
53
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.jpg?raw=true" alt="Feature" width="700"> | **JavaScript-Powered Content Management**<br><br>Harness the flexibility of JavaScript to define and manage your content efficiently. <br><br> - [Content declaration](https://intlayer.org/doc/concept/content) |
|
|
54
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true" alt="Feature" width="700"> | **Per-Locale Content Declaration File**<br><br>Speed up your development by declaring your content once, before auto generation.<br><br> - [Per-Locale Content Declaration File](https://intlayer.org/doc/concept/per-locale-file) |
|
|
55
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **Compiler**<br><br>The Intlayer Compiler extract automatically the content from the components and generate the dictionary files.<br><br> - [Compiler](https://intlayer.org/doc/compiler) |
|
|
56
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true" alt="Feature" width="700"> | **Type-Safe Environment**<br><br>Leverage TypeScript to ensure your content definitions and code are error-free, while also benefiting from IDE autocompletion.<br><br> - [TypeScript configuration](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
|
|
57
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true" alt="Feature" width="700"> | **Simplified Setup**<br><br>Get up and running quickly with minimal configuration. Adjust settings for internationalization, routing, AI, build, and content handling with ease. <br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
|
|
58
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/content_retrieval.png?raw=true" alt="Feature" width="700"> | **Simplified Content Retrieval**<br><br>No need to call your `t` function for each piece of content. Retrieve all your content directly using a single hook.<br><br> - [React integration](https://intlayer.org/doc/environment/create-react-app) |
|
|
59
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/server_component.png?raw=true" alt="Feature" width="700"> | **Consistent Server Component Implementation**<br><br>Perfectly suited for Next.js server components, use the same implementation for both client and server components, no need to pass your `t` function across each server component. <br><br> - [Server Components](https://intlayer.org/doc/environment/nextjs#step-7-utilize-content-in-your-code) |
|
|
60
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/file_tree.png?raw=true" alt="Feature" width="700"> | **Organized Codebase**<br><br>Keep your codebase more organized: 1 component = 1 dictionary in the same folder. Translations close to their respective components, enhance maintainability and clarity. <br><br> - [How Intlayer works](https://intlayer.org/doc/concept/how-works-intlayer) |
|
|
61
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/url_routing.png?raw=true" alt="Feature" width="700"> | **Enhanced Routing**<br><br>Full support of app routing, adapting seamlessly to complex application structures, for Next.js, React, Vite, Vue.js, etc.<br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
|
|
62
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/markdown.png?raw=true" alt="Feature" width="700"> | **Markdown Support**<br><br>Import and interpret, locale files and remote Markdown for multilingual content like privacy policies, documentation, etc. Interpret and make Markdown metadata accessible in your code.<br><br> - [Content files](https://intlayer.org/doc/concept/content/file) |
|
|
63
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/visual_editor.png?raw=true" alt="Feature" width="700"> | **Free Visual Editor & CMS**<br><br>A free visual editor and CMS are available for content writers, removing the need for a localization platform. Keep your content synchronized using Git, or externalize it totally or partially with the CMS.<br><br> - [Intlayer Editor](https://intlayer.org/doc/concept/editor) <br> - [Intlayer CMS](https://intlayer.org/doc/concept/cms) |
|
|
64
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true" alt="Feature" width="700"> | **Tree-shakable Content**<br><br>Tree-shakable content, reducing the size of the final bundle. Loads content per component, excluding any unused content from your bundle. Supports lazy loading to enhance app loading efficiency. <br><br> - [App build optimization](https://intlayer.org/doc/concept/how-works-intlayer#app-build-optimization) |
|
|
65
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/static_rendering.png?raw=true" alt="Feature" width="700"> | **Static Rendering**<br><br>Doesn't block Static Rendering. <br><br> - [Next.js integration](https://intlayer.org/doc/environment/nextjs) |
|
|
66
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/AI_translation.png?raw=true" alt="Feature" width="700"> | **AI-Powered Translation**<br><br>Transform your website into 231 languages with just one click using Intlayer's advanced AI-powered translation tools using your own AI provider / API key. <br><br> - [CI/CD integration](https://intlayer.org/doc/concept/ci-cd) <br> - [Intlayer CLI](https://intlayer.org/doc/concept/cli) <br> - [Auto fill](https://intlayer.org/doc/concept/auto-fill) |
|
|
67
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true" alt="Feature" width="700"> | **MCP Server Integration**<br><br>Provides an MCP (Model Context Protocol) server for IDE automation, enabling seamless content management and i18n workflows directly within your development environment. <br><br> - [MCP Server](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md) |
|
|
68
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true" alt="Feature" width="700"> | **VSCode Extension**<br><br>Intlayer provides a VSCode extension to help you manage your content and translations, building your dictionaries, translating your content, and more. <br><br> - [VSCode Extension](https://intlayer.org/doc/vs-code-extension) |
|
|
69
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true" alt="Feature" width="700"> | **Interoperability**<br><br>Allow interoperability with react-i18next, next-i18next, next-intl, react-intl, vue-i18n. <br><br> - [Intlayer and react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer and next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer and next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) <br> - [Intlayer and vue-i18n](https://intlayer.org/blog/intlayer-with-vue-i18n) <br> - [Intlayer compat adapters](https://intlayer.org/doc/compatibility) |
|
|
70
|
+
| <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/benchmark.png?raw=true" alt="Feature" width="700"> | **Performances & Benchmark**<br><br>Uses advanced tree-shaking and dynamic loading to boost performances and keep the solution as light as possible. <br><br> - [Performances & Benchmark](https://intlayer.org/doc/benchmark) |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 📦 Installation
|
|
75
|
+
|
|
76
|
+
Start your journey with Intlayer today and experience a smoother, more powerful approach to internationalization.
|
|
77
|
+
|
|
78
|
+
<a href="https://intlayer.org/doc/concept/content" rel="">
|
|
79
|
+
<img src="https://img.shields.io/badge/Get_Started-FFFFFF?style=for-the-badge&logo=rocket&logoColor=black" />
|
|
80
|
+
</a>
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install intlayer react-intlayer
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
⚡ Quick Start (Next.js)
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// intlayer.config.ts
|
|
90
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
91
|
+
|
|
92
|
+
const config: IntlayerConfig = {
|
|
93
|
+
internationalization: {
|
|
94
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
95
|
+
defaultLocale: Locales.ENGLISH,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default config;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// app/home.content.ts
|
|
104
|
+
import { t, type Dictionary } from "intlayer";
|
|
105
|
+
|
|
106
|
+
const content = {
|
|
107
|
+
key: "home",
|
|
108
|
+
content: {
|
|
109
|
+
title: t({
|
|
110
|
+
en: "Home",
|
|
111
|
+
fr: "Accueil",
|
|
112
|
+
es: "Inicio",
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
} satisfies Dictionary;
|
|
116
|
+
|
|
117
|
+
export default content;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// app/page.tsx
|
|
122
|
+
import { useIntlayer } from "react-intlayer";
|
|
123
|
+
|
|
124
|
+
const HomePage = () => {
|
|
125
|
+
const { title } = useIntlayer("home");
|
|
126
|
+
|
|
127
|
+
return <h1>{title}</h1>;
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
<a href="https://intlayer.org/doc/environment/nextjs"> Get the full guide → </a>
|
|
132
|
+
|
|
133
|
+
## 🎥 Live tutorial on YouTube
|
|
134
|
+
|
|
135
|
+
[](https://youtu.be/e_PPG7PTqGU?si=GyU_KpVhr61razRw)
|
|
136
|
+
|
|
137
|
+
<a href="https://intlayer.org/doc/concept/content" rel="">
|
|
138
|
+
<img src="https://img.shields.io/badge/Get_Started-FFFFFF?style=for-the-badge&logo=rocket&logoColor=black" />
|
|
139
|
+
</a>
|
|
140
|
+
|
|
141
|
+
## Table of Contents
|
|
142
|
+
|
|
143
|
+
Explore our comprehensive documentation to get started with Intlayer and learn how to integrate it into your projects.
|
|
144
|
+
|
|
145
|
+
<details open>
|
|
146
|
+
<summary style="font-size:16px; font-weight:bold;">📘 Get Started</summary>
|
|
147
|
+
<ul>
|
|
148
|
+
<li><a href="https://intlayer.org/doc/why" rel=''>Why Intlayer?</a></li>
|
|
149
|
+
<li><a href="https://intlayer.org/doc" rel=''>Introduction</a></li>
|
|
150
|
+
</ul>
|
|
151
|
+
</details>
|
|
152
|
+
|
|
153
|
+
<details>
|
|
154
|
+
<summary style="font-size:16px; font-weight:bold;">⚙️ Concept</summary>
|
|
155
|
+
<ul>
|
|
156
|
+
<li><a href="https://intlayer.org/doc/concept/how-works-intlayer" rel=''>How Intlayer Works</a></li>
|
|
157
|
+
<li><a href="https://intlayer.org/doc/concept/configuration" rel=''>Configuration</a></li>
|
|
158
|
+
<li><a href="https://intlayer.org/doc/concept/cli" rel=''>Intlayer CLI</a></li>
|
|
159
|
+
<li><a href="https://intlayer.org/doc/compiler" rel=''>Compiler</a></li>
|
|
160
|
+
|
|
161
|
+
<li><a href="https://intlayer.org/doc/concept/editor" rel=''>Intlayer Editor</a></li>
|
|
162
|
+
<li><a href="https://intlayer.org/doc/concept/cms" rel=''>Intlayer CMS</a></li>
|
|
163
|
+
<li><a href="https://intlayer.org/doc/concept/content" rel=''>Dictionary</a>
|
|
164
|
+
<ul>
|
|
165
|
+
<li><a href="https://intlayer.org/doc/concept/per-locale-file" rel=''>Per-Locale Content Declaration File</a></li>
|
|
166
|
+
<li><a href="https://intlayer.org/doc/concept/content/translation" rel=''>Translation</a></li>
|
|
167
|
+
<li><a href="https://intlayer.org/doc/concept/content/enumeration" rel=''>Enumeration</a></li>
|
|
168
|
+
<li><a href="https://intlayer.org/doc/concept/content/condition" rel=''>Condition</a></li>
|
|
169
|
+
<li><a href="https://intlayer.org/doc/concept/content/nesting" rel=''>Nesting</a></li>
|
|
170
|
+
<li><a href="https://intlayer.org/doc/concept/content/markdown" rel=''>Markdown</a></li>
|
|
171
|
+
<li><a href="https://intlayer.org/doc/concept/content/function-fetching" rel=''>Function Fetching</a></li>
|
|
172
|
+
<li><a href="https://intlayer.org/doc/concept/content/insertion" rel=''>Insertion</a></li>
|
|
173
|
+
<li><a href="https://intlayer.org/doc/concept/content/file" rel=''>File</a></li>
|
|
174
|
+
</ul>
|
|
175
|
+
</li>
|
|
176
|
+
</ul>
|
|
177
|
+
</details>
|
|
178
|
+
|
|
179
|
+
<details open>
|
|
180
|
+
<summary style="font-size:16px; font-weight:bold;">🌐 Environment</summary>
|
|
181
|
+
<ul>
|
|
182
|
+
<li><a href="https://intlayer.org/doc/environment/nextjs" rel=''>Intlayer with Next.js 16</a>
|
|
183
|
+
<ul>
|
|
184
|
+
<li><a href="https://intlayer.org/doc/environment/nextjs/15" rel=''>Next.js 15</a></li>
|
|
185
|
+
<li><a href="https://intlayer.org/doc/environment/nextjs/14" rel=''>Next.js 14 (App Router)</a></li>
|
|
186
|
+
<li><a href="https://intlayer.org/doc/environment/nextjs/next-with-page-router" rel=''>Next.js Page Router</a></li>
|
|
187
|
+
<li><a href="https://intlayer.org/doc/environment/nextjs/compiler" rel=''>Next.js using Compiler</a></li>
|
|
188
|
+
</ul>
|
|
189
|
+
</li>
|
|
190
|
+
<li><a href="https://intlayer.org/doc/environment/create-react-app" rel=''>React CRA</a></li>
|
|
191
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-react" rel=''>Vite + React</a></li>
|
|
192
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-react" rel=''>Vite + React using Compiler</a></li>
|
|
193
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-react/compiler" rel=''>React-router-v7</a></li>
|
|
194
|
+
<li><a href="https://intlayer.org/doc/environment/tanstack-start" rel=''>Tanstack start</a>
|
|
195
|
+
<ul>
|
|
196
|
+
<li><a href="https://intlayer.org/doc/environment/tanstack-start/solid" rel=''>Solid</a></li>
|
|
197
|
+
</ul>
|
|
198
|
+
</li>
|
|
199
|
+
<li><a href="https://intlayer.org/doc/environment/astro" rel=''>Astro</a>
|
|
200
|
+
<ul>
|
|
201
|
+
<li><a href="https://intlayer.org/doc/environment/astro/react" rel=''>React</a></li>
|
|
202
|
+
<li><a href="https://intlayer.org/doc/environment/astro/vue" rel=''>Vue</a></li>
|
|
203
|
+
<li><a href="https://intlayer.org/doc/environment/astro/svelte" rel=''>Svelte</a></li>
|
|
204
|
+
<li><a href="https://intlayer.org/doc/environment/astro/solid" rel=''>Solid</a></li>
|
|
205
|
+
<li><a href="https://intlayer.org/doc/environment/astro/vanilla" rel=''>Vanilla JS</a></li>
|
|
206
|
+
<li><a href="https://intlayer.org/doc/environment/astro/lit" rel=''>Lit</a></li>
|
|
207
|
+
</ul>
|
|
208
|
+
</li>
|
|
209
|
+
|
|
210
|
+
<li><a href="https://intlayer.org/doc/environment/react-native-and-expo" rel=''>React Native</a></li>
|
|
211
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-svelte" rel=''>Vite + Svelte</a></li>
|
|
212
|
+
<li><a href="https://intlayer.org/doc/environment/sveltekit" rel=''>SvelteKit</a></li>
|
|
213
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-preact" rel=''>Vite + Preact</a></li>
|
|
214
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-vue" rel=''>Vite + Vue</a></li>
|
|
215
|
+
<li><a href="https://intlayer.org/doc/environment/nuxt-and-vue" rel=''>Vite + Nuxt</a></li>
|
|
216
|
+
<li><a href="https://intlayer.org/doc/environment/vite-and-solid" rel=''>Vite + Solid</a></li>
|
|
217
|
+
<li><a href="https://intlayer.org/doc/environment/angular" rel=''>Angular</a></li>
|
|
218
|
+
<li>
|
|
219
|
+
<a href="https://intlayer.org/doc/environment/express" rel=''>Backend</a>
|
|
220
|
+
<ul>
|
|
221
|
+
<li><a href="https://intlayer.org/doc/environment/express" rel=''>Express</a></li>
|
|
222
|
+
<li><a href="https://intlayer.org/doc/environment/nest" rel=''>NestJS</a></li>
|
|
223
|
+
<li><a href="https://intlayer.org/doc/environment/fastify" rel=''>Fastify</a></li>
|
|
224
|
+
<li><a href="https://intlayer.org/doc/environment/adonisjs" rel=''>AdonisJS</a></li>
|
|
225
|
+
<li><a href="https://intlayer.org/doc/environment/hono" rel=''>Hono</a></li>
|
|
226
|
+
<li><a href="https://intlayer.org/doc/environment/elysia" rel=''>Elysia</a></li>
|
|
227
|
+
<li><a href="https://intlayer.org/doc/environment/elysia" rel=''>Elysia</a></li>
|
|
228
|
+
</ul>
|
|
229
|
+
</li>
|
|
230
|
+
</ul>
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
<details>
|
|
234
|
+
<summary style="font-size:16px; font-weight:bold;">📊 Benchmark</summary>
|
|
235
|
+
<ul>
|
|
236
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/nextjs.md" rel=''>Next.js</a></li>
|
|
237
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/tanstack.md" rel=''>TanStack Start</a></li>
|
|
238
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/vue.md" rel=''>Vue</a></li>
|
|
239
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/solid.md" rel=''>Solid</a></li>
|
|
240
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/svelte.md" rel=''>Svelte</a></li>
|
|
241
|
+
</ul>
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
<details>
|
|
245
|
+
<summary style="font-size:16px; font-weight:bold;">📰 Blog</summary>
|
|
246
|
+
<ul>
|
|
247
|
+
<li><a href="https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/what_is_internationalization.md" rel=''>What is i18n</a></li>
|
|
248
|
+
<li><a href="https://intlayer.org/blog/SEO-and-i18n" rel=''>i18n and SEO</a></li>
|
|
249
|
+
<li><a href="https://intlayer.org/blog/intlayer-with-next-i18next" rel=''>Intlayer and i18next</a></li>
|
|
250
|
+
<li><a href="https://intlayer.org/blog/intlayer-with-react-i18next" rel=''>Intlayer and react-intl</a></li>
|
|
251
|
+
<li><a href="https://intlayer.org/blog/intlayer-with-next-intl" rel=''>Intlayer and next-intl</a></li>
|
|
252
|
+
</ul>
|
|
253
|
+
</details>
|
|
254
|
+
|
|
255
|
+
## Multilingual content management system
|
|
256
|
+
|
|
257
|
+
More than an i18n library, Intlayer is a complete **multilingual content management system**. A full CMS is available for free at [app.intlayer.org](https://app.intlayer.org).
|
|
258
|
+
|
|
259
|
+
Intlayer connects **developers**, **copywriters**, and **AI agents** in one workflow for creating and maintaining multilingual websites effortlessly.Intlayer replaces the following stack in a single solution:
|
|
260
|
+
|
|
261
|
+
- i18n solutions (e.g. `i18next`, `next-intl`, `vue-i18n`)
|
|
262
|
+
- TMSs (Translation Management Systems) (e.g. Crowdin, Phrase, Lokalise)
|
|
263
|
+
- Feature flags
|
|
264
|
+
- Headless CMSs (e.g. Contentful, Strapi, Sanity)
|
|
265
|
+
|
|
266
|
+

|
|
267
|
+
|
|
268
|
+
## 🌐 Readme in other languages
|
|
269
|
+
|
|
270
|
+
<p align="center">
|
|
271
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/readme.md">English</a> •
|
|
272
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/readme.md">简体中文</a> •
|
|
273
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/readme.md">Русский</a> •
|
|
274
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/readme.md">日本語</a> •
|
|
275
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/fr/readme.md">Français</a> •
|
|
276
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/ko/readme.md">한국어</a> •
|
|
277
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/es/readme.md">Español</a> •
|
|
278
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/de/readme.md">Deutsch</a> •
|
|
279
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/ar/readme.md">العربية</a> •
|
|
280
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/it/readme.md">Italiano</a> •
|
|
281
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/readme.md">English (UK)</a> •
|
|
282
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/readme.md">Português</a> •
|
|
283
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/hi/readme.md">हिन्दी</a> •
|
|
284
|
+
<a href="https://github.com/aymericzip/intlayer/blob/main/docs/docs/tr/readme.md">Türkçe</a>
|
|
285
|
+
</p>
|
|
286
|
+
|
|
287
|
+
## 🤝 Community
|
|
288
|
+
|
|
289
|
+
Intlayer is built with and for the community and we’d love your input!
|
|
290
|
+
|
|
291
|
+
- Have a suggestion? [Open an issue](https://github.com/aymericzip/intlayer/issues)
|
|
292
|
+
- Found a bug or improvement? [Submit a PR](https://github.com/aymericzip/intlayer/pulls)
|
|
293
|
+
- Need help or want to connect? [Join our Discord](https://discord.gg/7uxamYVeCk)
|
|
294
|
+
|
|
295
|
+
You can also follow us on :
|
|
296
|
+
|
|
297
|
+
<div>
|
|
298
|
+
<br/>
|
|
299
|
+
<p align="center">
|
|
300
|
+
<a href="https://discord.gg/528mBV4N" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
301
|
+
src="https://img.shields.io/badge/discord-5865F2.svg?style=for-the-badge&logo=discord&logoColor=white"
|
|
302
|
+
alt="Intlayer Discord" height="30"/></a>
|
|
303
|
+
<a href="https://www.linkedin.com/company/intlayerorg" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
304
|
+
src="https://img.shields.io/badge/linkedin-%231DA1F2.svg?style=for-the-badge&logo=linkedin&logoColor=white"
|
|
305
|
+
alt="Intlayer LinkedIn" height="30"/></a>
|
|
306
|
+
<a href="https://www.instagram.com/intlayer/" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
307
|
+
src="https://img.shields.io/badge/instagram-%23E4405F.svg?style=for-the-badge&logo=Instagram&logoColor=white"
|
|
308
|
+
alt="Intlayer Instagram" height="30"/></a>
|
|
309
|
+
<a href="https://x.com/Intlayer183096" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
310
|
+
src="https://img.shields.io/badge/x-1DA1F2.svg?style=for-the-badge&logo=x&logoColor=white"
|
|
311
|
+
alt="Intlayer X" height="30"/></a>
|
|
312
|
+
<a href="https://www.youtube.com/@intlayer" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
313
|
+
src="https://img.shields.io/badge/youtube-FF0000.svg?style=for-the-badge&logo=youtube&logoColor=white"
|
|
314
|
+
alt="Intlayer YouTube" height="30"/></a>
|
|
315
|
+
<a href="https://www.tiktok.com/@intlayer" target="blank" rel='noopener noreferrer nofollow'><img align="center"
|
|
316
|
+
src="https://img.shields.io/badge/tiktok-000000.svg?style=for-the-badge&logo=tiktok&logoColor=white"
|
|
317
|
+
alt="Intlayer TikTok" height="30"/></a>
|
|
318
|
+
<br>
|
|
319
|
+
</p>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
### Contribution
|
|
323
|
+
|
|
324
|
+
For more detailed guidelines on contributing to this project, please refer to the [`CONTRIBUTING.md`](https://github.com/aymericzip/intlayer/blob/main/CONTRIBUTING.md) file. It contains essential information on our development process, commit message conventions, and release procedures. Your contributions are valuable to us, and we appreciate your efforts in making this project better!
|
|
325
|
+
|
|
326
|
+
Contribute on [GitHub](https://github.com/aymericzip/intlayer), [GitLab](https://gitlab.com/ay.pineau/intlayer), or [Bitbucket](https://bitbucket.org/intlayer/intlayer/).
|
|
327
|
+
|
|
328
|
+
### Thank You for the Support
|
|
329
|
+
|
|
330
|
+
If you like Intlayer, give us a ⭐ on GitHub. It helps others discover the project! [See why GitHub Stars matter](https://github.com/aymericzip/intlayer/blob/main/CONTRIBUTING.md#why-github-stars-matter-).
|
|
331
|
+
|
|
332
|
+
[](https://star-history.dera.page/#aymericzip/intlayer&Date)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
exports.__toESM = __toESM;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_async_hooks = require("node:async_hooks");
|
|
3
|
+
let _intlayer_config_node = require("@intlayer/config/node");
|
|
4
|
+
let _intlayer_core_interpreter = require("@intlayer/core/interpreter");
|
|
5
|
+
let _intlayer_core_localization = require("@intlayer/core/localization");
|
|
6
|
+
let _intlayer_core_utils = require("@intlayer/core/utils");
|
|
7
|
+
let _intlayer_engine_build = require("@intlayer/engine/build");
|
|
8
|
+
let elysia = require("elysia");
|
|
9
|
+
|
|
10
|
+
//#region src/index.ts
|
|
11
|
+
/**
|
|
12
|
+
* Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.
|
|
13
|
+
*
|
|
14
|
+
* `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,
|
|
15
|
+
* which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.
|
|
16
|
+
*/
|
|
17
|
+
const intlayerStorage = new node_async_hooks.AsyncLocalStorage();
|
|
18
|
+
/**
|
|
19
|
+
* Returns the context of the request currently being handled, if any.
|
|
20
|
+
*/
|
|
21
|
+
const getRequestContext = () => intlayerStorage.getStore()?.current;
|
|
22
|
+
let debug = () => {};
|
|
23
|
+
/**
|
|
24
|
+
* Builds the translation function bound to a resolved request locale.
|
|
25
|
+
*/
|
|
26
|
+
const createTranslateFunction = (locale, defaultLocale) => (content, localeArg) => {
|
|
27
|
+
const targetLocale = localeArg ?? locale;
|
|
28
|
+
if (typeof content === "undefined") return "";
|
|
29
|
+
if (typeof content === "string") return content;
|
|
30
|
+
if (typeof content?.[targetLocale] === "undefined") {
|
|
31
|
+
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
32
|
+
return (0, _intlayer_core_interpreter.getTranslation)(content, defaultLocale);
|
|
33
|
+
}
|
|
34
|
+
return (0, _intlayer_core_interpreter.getTranslation)(content, targetLocale);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Releases the request context once the response is mapped, so the standalone helpers
|
|
38
|
+
* never resolve against an already terminated request.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.
|
|
41
|
+
*/
|
|
42
|
+
const releaseContext = () => {
|
|
43
|
+
const contextRef = intlayerStorage.getStore();
|
|
44
|
+
if (contextRef) contextRef.current = void 0;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Elysia plugin that integrates Intlayer into your Elysia application.
|
|
48
|
+
*
|
|
49
|
+
* It handles:
|
|
50
|
+
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
51
|
+
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
52
|
+
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
53
|
+
* for the duration of the request, through `AsyncLocalStorage`.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { Elysia } from 'elysia';
|
|
58
|
+
* import { intlayer } from 'elysia-intlayer';
|
|
59
|
+
*
|
|
60
|
+
* const app = new Elysia()
|
|
61
|
+
* .use(intlayer())
|
|
62
|
+
* .get('/', ({ intlayer }) =>
|
|
63
|
+
* intlayer.t({
|
|
64
|
+
* en: 'Hello',
|
|
65
|
+
* fr: 'Bonjour',
|
|
66
|
+
* })
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
const intlayer = () => {
|
|
71
|
+
const configuration = (0, _intlayer_config_node.getConfiguration)();
|
|
72
|
+
const { internationalization } = configuration;
|
|
73
|
+
if (process.env["NODE_ENV"] === "development") debug = (message) => console.debug(message);
|
|
74
|
+
(0, _intlayer_engine_build.prepareIntlayer)(configuration);
|
|
75
|
+
return new elysia.Elysia({ name: "elysia-intlayer" }).derive({ as: "global" }, ({ request, cookie }) => {
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves the locale from storage (cookies, headers).
|
|
78
|
+
*/
|
|
79
|
+
const localeFromStorage = (0, _intlayer_core_utils.getLocaleFromStorageServer)({
|
|
80
|
+
getCookie: (name) => cookie?.[name]?.value,
|
|
81
|
+
getHeader: (name) => request.headers.get(name) ?? void 0
|
|
82
|
+
});
|
|
83
|
+
const negotiatorHeaders = {};
|
|
84
|
+
request.headers.forEach((value, key) => {
|
|
85
|
+
negotiatorHeaders[key] = value;
|
|
86
|
+
});
|
|
87
|
+
const localeDetected = (0, _intlayer_core_localization.localeDetector)(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
88
|
+
const locale = localeFromStorage ?? localeDetected;
|
|
89
|
+
const defaultLocale = internationalization.defaultLocale;
|
|
90
|
+
const getIntlayer = (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getIntlayer)(key, localeArg, ...props);
|
|
91
|
+
const getDictionary = (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getDictionary)(key, localeArg, ...props);
|
|
92
|
+
const context = {
|
|
93
|
+
locale_storage: localeFromStorage,
|
|
94
|
+
locale_detected: localeDetected,
|
|
95
|
+
locale,
|
|
96
|
+
defaultLocale,
|
|
97
|
+
t: createTranslateFunction(locale, defaultLocale),
|
|
98
|
+
getIntlayer,
|
|
99
|
+
getDictionary
|
|
100
|
+
};
|
|
101
|
+
intlayerStorage.enterWith({ current: context });
|
|
102
|
+
return { intlayer: context };
|
|
103
|
+
}).mapResponse({ as: "global" }, releaseContext).onError({ as: "global" }, releaseContext);
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Translation function that retrieves content for the locale of the current request.
|
|
107
|
+
*
|
|
108
|
+
* Falls back to the configured default locale when called outside of a request
|
|
109
|
+
* handled by the `intlayer` plugin.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { t } from 'elysia-intlayer';
|
|
114
|
+
*
|
|
115
|
+
* app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
const t = (content, locale) => {
|
|
119
|
+
const context = getRequestContext();
|
|
120
|
+
if (context) return context.t(content, locale);
|
|
121
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.");
|
|
122
|
+
const { internationalization } = (0, _intlayer_config_node.getConfiguration)();
|
|
123
|
+
return (0, _intlayer_core_interpreter.getTranslation)(content, locale ?? internationalization.defaultLocale);
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Retrieves a dictionary by key, using the locale of the current request by default.
|
|
127
|
+
*/
|
|
128
|
+
const getIntlayer = (...args) => {
|
|
129
|
+
const context = getRequestContext();
|
|
130
|
+
if (context) return context.getIntlayer(...args);
|
|
131
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.");
|
|
132
|
+
return (0, _intlayer_core_interpreter.getIntlayer)(...args);
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves an imported dictionary, using the locale of the current request by default.
|
|
136
|
+
*/
|
|
137
|
+
const getDictionary = (...args) => {
|
|
138
|
+
const context = getRequestContext();
|
|
139
|
+
if (context) return context.getDictionary(...args);
|
|
140
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.");
|
|
141
|
+
return (0, _intlayer_core_interpreter.getDictionary)(...args);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
exports.getDictionary = getDictionary;
|
|
146
|
+
exports.getIntlayer = getIntlayer;
|
|
147
|
+
exports.intlayer = intlayer;
|
|
148
|
+
exports.t = t;
|
|
149
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["AsyncLocalStorage","getTranslation","getConfiguration","Elysia","getLocaleFromStorageServer","localeDetector","getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Mutable holder for the context of the request being handled.\n *\n * The context is held behind a reference because `AsyncLocalStorage.enterWith(undefined)`\n * is a no-op in Bun: clearing the reference is the only reliable way to release it.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n *\n * `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,\n * which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Builds the translation function bound to a resolved request locale.\n */\nconst createTranslateFunction =\n (locale: Locale, defaultLocale: Locale): TranslateFunction =>\n <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n localeArg?: Locale\n ): Content => {\n const targetLocale = localeArg ?? locale;\n\n if (typeof content === 'undefined') {\n return '' as unknown as Content;\n }\n\n if (typeof content === 'string') {\n return content as unknown as Content;\n }\n\n if (\n typeof content?.[\n targetLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n if (\n typeof content?.[\n defaultLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n return content as unknown as Content;\n }\n\n return getTranslation(content, defaultLocale);\n }\n\n return getTranslation(content, targetLocale);\n };\n\n/**\n * Releases the request context once the response is mapped, so the standalone helpers\n * never resolve against an already terminated request.\n *\n * Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.\n */\nconst releaseContext = (): undefined => {\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = undefined;\n }\n\n return undefined;\n};\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the duration of the request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n const configuration = getConfiguration();\n const { internationalization } = configuration;\n\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayer(configuration);\n\n return new Elysia({ name: 'elysia-intlayer' })\n .derive({ as: 'global' }, ({ request, cookie }) => {\n /**\n * Retrieves the locale from storage (cookies, headers).\n */\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n cookie?.[name]?.value as string | undefined,\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const negotiatorHeaders: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n negotiatorHeaders[key] = value;\n });\n\n const localeDetected = localeDetector(\n negotiatorHeaders,\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n const defaultLocale = internationalization.defaultLocale;\n\n const getIntlayer: typeof getIntlayerFunction = (\n key: Parameters<typeof getIntlayerFunction>[0],\n localeArg = locale as Parameters<typeof getIntlayerFunction>[1],\n ...props: any[]\n ) => getIntlayerFunction(key, localeArg, ...props);\n\n const getDictionary: typeof getDictionaryFunction = (\n key: Parameters<typeof getDictionaryFunction>[0],\n localeArg = locale as Parameters<typeof getDictionaryFunction>[1],\n ...props: any[]\n ) => getDictionaryFunction(key, localeArg, ...props);\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: createTranslateFunction(locale, defaultLocale),\n getIntlayer,\n getDictionary,\n };\n\n // Elysia awaits every lifecycle hook of a request on the same async chain,\n // so entering the store here keeps it available to the route handler.\n intlayerStorage.enterWith({ current: context });\n\n return { intlayer: context };\n })\n .mapResponse({ as: 'global' }, releaseContext)\n .onError({ as: 'global' }, releaseContext);\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n const { internationalization } = getConfiguration();\n\n return getTranslation(\n content as StrictModeLocaleMap<Content>,\n locale ?? internationalization.defaultLocale\n );\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (\n ...args: Parameters<typeof getIntlayerFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (\n ...args: Parameters<typeof getDictionaryFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAwDA,MAAM,kBAAkB,IAAIA,mCAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;AAG9B,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,2BACH,QAAgB,mBAEf,SACA,cACY;CACZ,MAAM,eAAe,aAAa;CAElC,IAAI,OAAO,YAAY,aACrB,OAAO;CAGT,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,IACE,OAAO,UACL,kBACI,aACN;EACA,IACE,OAAO,UACL,mBACI,aAEN,OAAO;EAGT,WAAOC,2CAAe,SAAS,aAAa;CAC9C;CAEA,WAAOA,2CAAe,SAAS,YAAY;AAC7C;;;;;;;AAQF,MAAM,uBAAkC;CACtC,MAAM,aAAa,gBAAgB,SAAS;CAE5C,IAAI,YACF,WAAW,UAAU;AAIzB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB;CAC5B,MAAM,oBAAgBC,wCAAiB;CACvC,MAAM,EAAE,yBAAyB;CAEjC,IAAI,QAAQ,IAAI,gBAAgB,eAC9B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,4CAAgB,aAAa;CAE7B,OAAO,IAAIC,cAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAC3C,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;;;;EAIjD,MAAM,wBAAoBC,iDAA2B;GACnD,YAAY,SACV,SAAS,KAAK,EAAE;GAClB,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,oBAA4C,CAAC;EACnD,QAAQ,QAAQ,SAAS,OAAO,QAAQ;GACtC,kBAAkB,OAAO;EAC3B,CAAC;EAED,MAAM,qBAAiBC,4CACrB,mBACA,qBAAqB,SACrB,qBAAqB,aACvB;EAEA,MAAM,SAAS,qBAAqB;EACpC,MAAM,gBAAgB,qBAAqB;EAE3C,MAAM,eACJ,KACA,YAAY,QACZ,GAAG,cACAC,wCAAoB,KAAK,WAAW,GAAG,KAAK;EAEjD,MAAM,iBACJ,KACA,YAAY,QACZ,GAAG,cACAC,0CAAsB,KAAK,WAAW,GAAG,KAAK;EAEnD,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,GAAG,wBAAwB,QAAQ,aAAa;GAChD;GACA;EACF;EAIA,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAE9C,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC,CAAC,CACD,YAAY,EAAE,IAAI,SAAS,GAAG,cAAc,CAAC,CAC7C,QAAQ,EAAE,IAAI,SAAS,GAAG,cAAc;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,MAAM,EAAE,6BAAyBL,wCAAiB;CAElD,WAAOD,2CACL,SACA,UAAU,qBAAqB,aACjC;AACF;;;;AAKA,MAAa,eACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,WAAOK,wCAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,WAAOC,0CAAsB,GAAG,IAAI;AACtC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { getConfiguration } from "@intlayer/config/node";
|
|
3
|
+
import { getDictionary as getDictionary$1, getIntlayer as getIntlayer$1, getTranslation } from "@intlayer/core/interpreter";
|
|
4
|
+
import { localeDetector } from "@intlayer/core/localization";
|
|
5
|
+
import { getLocaleFromStorageServer } from "@intlayer/core/utils";
|
|
6
|
+
import { prepareIntlayer } from "@intlayer/engine/build";
|
|
7
|
+
import { Elysia } from "elysia";
|
|
8
|
+
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
/**
|
|
11
|
+
* Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.
|
|
12
|
+
*
|
|
13
|
+
* `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,
|
|
14
|
+
* which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.
|
|
15
|
+
*/
|
|
16
|
+
const intlayerStorage = new AsyncLocalStorage();
|
|
17
|
+
/**
|
|
18
|
+
* Returns the context of the request currently being handled, if any.
|
|
19
|
+
*/
|
|
20
|
+
const getRequestContext = () => intlayerStorage.getStore()?.current;
|
|
21
|
+
let debug = () => {};
|
|
22
|
+
/**
|
|
23
|
+
* Builds the translation function bound to a resolved request locale.
|
|
24
|
+
*/
|
|
25
|
+
const createTranslateFunction = (locale, defaultLocale) => (content, localeArg) => {
|
|
26
|
+
const targetLocale = localeArg ?? locale;
|
|
27
|
+
if (typeof content === "undefined") return "";
|
|
28
|
+
if (typeof content === "string") return content;
|
|
29
|
+
if (typeof content?.[targetLocale] === "undefined") {
|
|
30
|
+
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
31
|
+
return getTranslation(content, defaultLocale);
|
|
32
|
+
}
|
|
33
|
+
return getTranslation(content, targetLocale);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Releases the request context once the response is mapped, so the standalone helpers
|
|
37
|
+
* never resolve against an already terminated request.
|
|
38
|
+
*
|
|
39
|
+
* Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.
|
|
40
|
+
*/
|
|
41
|
+
const releaseContext = () => {
|
|
42
|
+
const contextRef = intlayerStorage.getStore();
|
|
43
|
+
if (contextRef) contextRef.current = void 0;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Elysia plugin that integrates Intlayer into your Elysia application.
|
|
47
|
+
*
|
|
48
|
+
* It handles:
|
|
49
|
+
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
50
|
+
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
51
|
+
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
52
|
+
* for the duration of the request, through `AsyncLocalStorage`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { Elysia } from 'elysia';
|
|
57
|
+
* import { intlayer } from 'elysia-intlayer';
|
|
58
|
+
*
|
|
59
|
+
* const app = new Elysia()
|
|
60
|
+
* .use(intlayer())
|
|
61
|
+
* .get('/', ({ intlayer }) =>
|
|
62
|
+
* intlayer.t({
|
|
63
|
+
* en: 'Hello',
|
|
64
|
+
* fr: 'Bonjour',
|
|
65
|
+
* })
|
|
66
|
+
* );
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
const intlayer = () => {
|
|
70
|
+
const configuration = getConfiguration();
|
|
71
|
+
const { internationalization } = configuration;
|
|
72
|
+
debug = (message) => console.debug(message);
|
|
73
|
+
prepareIntlayer(configuration);
|
|
74
|
+
return new Elysia({ name: "elysia-intlayer" }).derive({ as: "global" }, ({ request, cookie }) => {
|
|
75
|
+
/**
|
|
76
|
+
* Retrieves the locale from storage (cookies, headers).
|
|
77
|
+
*/
|
|
78
|
+
const localeFromStorage = getLocaleFromStorageServer({
|
|
79
|
+
getCookie: (name) => cookie?.[name]?.value,
|
|
80
|
+
getHeader: (name) => request.headers.get(name) ?? void 0
|
|
81
|
+
});
|
|
82
|
+
const negotiatorHeaders = {};
|
|
83
|
+
request.headers.forEach((value, key) => {
|
|
84
|
+
negotiatorHeaders[key] = value;
|
|
85
|
+
});
|
|
86
|
+
const localeDetected = localeDetector(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
87
|
+
const locale = localeFromStorage ?? localeDetected;
|
|
88
|
+
const defaultLocale = internationalization.defaultLocale;
|
|
89
|
+
const getIntlayer = (key, localeArg = locale, ...props) => getIntlayer$1(key, localeArg, ...props);
|
|
90
|
+
const getDictionary = (key, localeArg = locale, ...props) => getDictionary$1(key, localeArg, ...props);
|
|
91
|
+
const context = {
|
|
92
|
+
locale_storage: localeFromStorage,
|
|
93
|
+
locale_detected: localeDetected,
|
|
94
|
+
locale,
|
|
95
|
+
defaultLocale,
|
|
96
|
+
t: createTranslateFunction(locale, defaultLocale),
|
|
97
|
+
getIntlayer,
|
|
98
|
+
getDictionary
|
|
99
|
+
};
|
|
100
|
+
intlayerStorage.enterWith({ current: context });
|
|
101
|
+
return { intlayer: context };
|
|
102
|
+
}).mapResponse({ as: "global" }, releaseContext).onError({ as: "global" }, releaseContext);
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Translation function that retrieves content for the locale of the current request.
|
|
106
|
+
*
|
|
107
|
+
* Falls back to the configured default locale when called outside of a request
|
|
108
|
+
* handled by the `intlayer` plugin.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { t } from 'elysia-intlayer';
|
|
113
|
+
*
|
|
114
|
+
* app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
const t = (content, locale) => {
|
|
118
|
+
const context = getRequestContext();
|
|
119
|
+
if (context) return context.t(content, locale);
|
|
120
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.");
|
|
121
|
+
const { internationalization } = getConfiguration();
|
|
122
|
+
return getTranslation(content, locale ?? internationalization.defaultLocale);
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Retrieves a dictionary by key, using the locale of the current request by default.
|
|
126
|
+
*/
|
|
127
|
+
const getIntlayer = (...args) => {
|
|
128
|
+
const context = getRequestContext();
|
|
129
|
+
if (context) return context.getIntlayer(...args);
|
|
130
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.");
|
|
131
|
+
return getIntlayer$1(...args);
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Retrieves an imported dictionary, using the locale of the current request by default.
|
|
135
|
+
*/
|
|
136
|
+
const getDictionary = (...args) => {
|
|
137
|
+
const context = getRequestContext();
|
|
138
|
+
if (context) return context.getDictionary(...args);
|
|
139
|
+
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.");
|
|
140
|
+
return getDictionary$1(...args);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
export { getDictionary, getIntlayer, intlayer, t };
|
|
145
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Mutable holder for the context of the request being handled.\n *\n * The context is held behind a reference because `AsyncLocalStorage.enterWith(undefined)`\n * is a no-op in Bun: clearing the reference is the only reliable way to release it.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n *\n * `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,\n * which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Builds the translation function bound to a resolved request locale.\n */\nconst createTranslateFunction =\n (locale: Locale, defaultLocale: Locale): TranslateFunction =>\n <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n localeArg?: Locale\n ): Content => {\n const targetLocale = localeArg ?? locale;\n\n if (typeof content === 'undefined') {\n return '' as unknown as Content;\n }\n\n if (typeof content === 'string') {\n return content as unknown as Content;\n }\n\n if (\n typeof content?.[\n targetLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n if (\n typeof content?.[\n defaultLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n return content as unknown as Content;\n }\n\n return getTranslation(content, defaultLocale);\n }\n\n return getTranslation(content, targetLocale);\n };\n\n/**\n * Releases the request context once the response is mapped, so the standalone helpers\n * never resolve against an already terminated request.\n *\n * Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.\n */\nconst releaseContext = (): undefined => {\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = undefined;\n }\n\n return undefined;\n};\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the duration of the request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n const configuration = getConfiguration();\n const { internationalization } = configuration;\n\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayer(configuration);\n\n return new Elysia({ name: 'elysia-intlayer' })\n .derive({ as: 'global' }, ({ request, cookie }) => {\n /**\n * Retrieves the locale from storage (cookies, headers).\n */\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n cookie?.[name]?.value as string | undefined,\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const negotiatorHeaders: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n negotiatorHeaders[key] = value;\n });\n\n const localeDetected = localeDetector(\n negotiatorHeaders,\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n const defaultLocale = internationalization.defaultLocale;\n\n const getIntlayer: typeof getIntlayerFunction = (\n key: Parameters<typeof getIntlayerFunction>[0],\n localeArg = locale as Parameters<typeof getIntlayerFunction>[1],\n ...props: any[]\n ) => getIntlayerFunction(key, localeArg, ...props);\n\n const getDictionary: typeof getDictionaryFunction = (\n key: Parameters<typeof getDictionaryFunction>[0],\n localeArg = locale as Parameters<typeof getDictionaryFunction>[1],\n ...props: any[]\n ) => getDictionaryFunction(key, localeArg, ...props);\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: createTranslateFunction(locale, defaultLocale),\n getIntlayer,\n getDictionary,\n };\n\n // Elysia awaits every lifecycle hook of a request on the same async chain,\n // so entering the store here keeps it available to the route handler.\n intlayerStorage.enterWith({ current: context });\n\n return { intlayer: context };\n })\n .mapResponse({ as: 'global' }, releaseContext)\n .onError({ as: 'global' }, releaseContext);\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n const { internationalization } = getConfiguration();\n\n return getTranslation(\n content as StrictModeLocaleMap<Content>,\n locale ?? internationalization.defaultLocale\n );\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (\n ...args: Parameters<typeof getIntlayerFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (\n ...args: Parameters<typeof getDictionaryFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;;;AAwDA,MAAM,kBAAkB,IAAI,kBAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;AAG9B,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,2BACH,QAAgB,mBAEf,SACA,cACY;CACZ,MAAM,eAAe,aAAa;CAElC,IAAI,OAAO,YAAY,aACrB,OAAO;CAGT,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,IACE,OAAO,UACL,kBACI,aACN;EACA,IACE,OAAO,UACL,mBACI,aAEN,OAAO;EAGT,OAAO,eAAe,SAAS,aAAa;CAC9C;CAEA,OAAO,eAAe,SAAS,YAAY;AAC7C;;;;;;;AAQF,MAAM,uBAAkC;CACtC,MAAM,aAAa,gBAAgB,SAAS;CAE5C,IAAI,YACF,WAAW,UAAU;AAIzB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB;CAC5B,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,EAAE,yBAAyB;CAG/B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,gBAAgB,aAAa;CAE7B,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAC3C,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;;;;EAIjD,MAAM,oBAAoB,2BAA2B;GACnD,YAAY,SACV,SAAS,KAAK,EAAE;GAClB,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,oBAA4C,CAAC;EACnD,QAAQ,QAAQ,SAAS,OAAO,QAAQ;GACtC,kBAAkB,OAAO;EAC3B,CAAC;EAED,MAAM,iBAAiB,eACrB,mBACA,qBAAqB,SACrB,qBAAqB,aACvB;EAEA,MAAM,SAAS,qBAAqB;EACpC,MAAM,gBAAgB,qBAAqB;EAE3C,MAAM,eACJ,KACA,YAAY,QACZ,GAAG,UACAA,cAAoB,KAAK,WAAW,GAAG,KAAK;EAEjD,MAAM,iBACJ,KACA,YAAY,QACZ,GAAG,UACAC,gBAAsB,KAAK,WAAW,GAAG,KAAK;EAEnD,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,GAAG,wBAAwB,QAAQ,aAAa;GAChD;GACA;EACF;EAIA,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAE9C,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC,CAAC,CACD,YAAY,EAAE,IAAI,SAAS,GAAG,cAAc,CAAC,CAC7C,QAAQ,EAAE,IAAI,SAAS,GAAG,cAAc;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,MAAM,EAAE,yBAAyB,iBAAiB;CAElD,OAAO,eACL,SACA,UAAU,qBAAqB,aACjC;AACF;;;;AAKA,MAAa,eACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,OAAOD,cAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,OAAOC,gBAAsB,GAAG,IAAI;AACtC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getDictionary as getDictionary$1, getIntlayer as getIntlayer$1 } from "@intlayer/core/interpreter";
|
|
2
|
+
import { Locale } from "@intlayer/types/allLocales";
|
|
3
|
+
import { StrictModeLocaleMap } from "@intlayer/types/module_augmentation";
|
|
4
|
+
import { Elysia } from "elysia";
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Translates a locale map into the content matching the current request locale.
|
|
8
|
+
*/
|
|
9
|
+
type TranslateFunction = <Content extends string>(content: StrictModeLocaleMap<Content> | string, locale?: Locale) => Content;
|
|
10
|
+
/**
|
|
11
|
+
* Intlayer state injected into every Elysia route context by the `intlayer` plugin.
|
|
12
|
+
*/
|
|
13
|
+
type IntlayerContext = {
|
|
14
|
+
/** Locale explicitly requested by the client through a cookie or a header. */
|
|
15
|
+
locale_storage?: Locale;
|
|
16
|
+
/** Locale negotiated from the request headers (`Accept-Language`). */
|
|
17
|
+
locale_detected: Locale;
|
|
18
|
+
/** Locale to use for this request, `locale_storage` taking precedence. */
|
|
19
|
+
locale: Locale;
|
|
20
|
+
/** Locale configured as fallback in `intlayer.config.ts`. */
|
|
21
|
+
defaultLocale: Locale;
|
|
22
|
+
/** Translates an inline locale map. */
|
|
23
|
+
t: TranslateFunction;
|
|
24
|
+
/** Reads a dictionary by key, defaulting to the request locale. */
|
|
25
|
+
getIntlayer: typeof getIntlayer$1;
|
|
26
|
+
/** Reads an imported dictionary, defaulting to the request locale. */
|
|
27
|
+
getDictionary: typeof getDictionary$1;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Elysia plugin that integrates Intlayer into your Elysia application.
|
|
31
|
+
*
|
|
32
|
+
* It handles:
|
|
33
|
+
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
34
|
+
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
35
|
+
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
36
|
+
* for the duration of the request, through `AsyncLocalStorage`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { Elysia } from 'elysia';
|
|
41
|
+
* import { intlayer } from 'elysia-intlayer';
|
|
42
|
+
*
|
|
43
|
+
* const app = new Elysia()
|
|
44
|
+
* .use(intlayer())
|
|
45
|
+
* .get('/', ({ intlayer }) =>
|
|
46
|
+
* intlayer.t({
|
|
47
|
+
* en: 'Hello',
|
|
48
|
+
* fr: 'Bonjour',
|
|
49
|
+
* })
|
|
50
|
+
* );
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare const intlayer: () => Elysia<"", {
|
|
54
|
+
decorator: {};
|
|
55
|
+
store: {};
|
|
56
|
+
derive: Partial<{
|
|
57
|
+
readonly intlayer: IntlayerContext;
|
|
58
|
+
}>;
|
|
59
|
+
resolve: {};
|
|
60
|
+
}, {
|
|
61
|
+
typebox: {};
|
|
62
|
+
error: {};
|
|
63
|
+
}, {
|
|
64
|
+
schema: {};
|
|
65
|
+
standaloneSchema: {};
|
|
66
|
+
macro: {};
|
|
67
|
+
macroFn: {};
|
|
68
|
+
parser: {};
|
|
69
|
+
response: {};
|
|
70
|
+
}, {}, {
|
|
71
|
+
derive: {};
|
|
72
|
+
resolve: {};
|
|
73
|
+
schema: {};
|
|
74
|
+
standaloneSchema: {};
|
|
75
|
+
response: {};
|
|
76
|
+
}, {
|
|
77
|
+
derive: {};
|
|
78
|
+
resolve: {};
|
|
79
|
+
schema: {};
|
|
80
|
+
standaloneSchema: {};
|
|
81
|
+
response: {};
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Translation function that retrieves content for the locale of the current request.
|
|
85
|
+
*
|
|
86
|
+
* Falls back to the configured default locale when called outside of a request
|
|
87
|
+
* handled by the `intlayer` plugin.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { t } from 'elysia-intlayer';
|
|
92
|
+
*
|
|
93
|
+
* app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare const t: <Content extends string>(content: StrictModeLocaleMap<Content> | string, locale?: Locale) => Content;
|
|
97
|
+
/**
|
|
98
|
+
* Retrieves a dictionary by key, using the locale of the current request by default.
|
|
99
|
+
*/
|
|
100
|
+
declare const getIntlayer: typeof getIntlayer$1;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves an imported dictionary, using the locale of the current request by default.
|
|
103
|
+
*/
|
|
104
|
+
declare const getDictionary: typeof getDictionary$1;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { IntlayerContext, TranslateFunction, getDictionary, getIntlayer, intlayer, t };
|
|
107
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;;;KAiBY,qBAAqB,wBAC/B,SAAS,oBAAoB,mBAC7B,SAAS,WACN;;;;KAKO;;EAEV,iBAAiB;;EAEjB,iBAAiB;;EAEjB,QAAQ;;EAER,eAAe;;EAEf,GAAG;;EAEH,oBAAoB;;EAEpB,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;cA0GX,gBAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgFR,IAAK,wBAAsB,SAC7B,oBAAoB,mBAAiB,SACrC,WACR;;;;cAsBU,oBAAoB;;;;cAmBpB,sBAAsB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "@intlayer/types/config";
|
|
2
|
+
import "@intlayer/types/dictionary";
|
|
3
|
+
import { LocalesValues } from "@intlayer/types/module_augmentation";
|
|
4
|
+
import "@intlayer/types/allLocales";
|
|
5
|
+
import "@intlayer/types/locales";
|
|
6
|
+
import "@intlayer/config/built";
|
|
7
|
+
import "@intlayer/core/file";
|
|
8
|
+
import "@intlayer/core/formatters";
|
|
9
|
+
import "@intlayer/core/interpreter";
|
|
10
|
+
import "@intlayer/core/localization";
|
|
11
|
+
import "@intlayer/core/markdown";
|
|
12
|
+
import "@intlayer/core/transpiler";
|
|
13
|
+
import "@intlayer/core/utils";
|
|
14
|
+
export type { LocalesValues };
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "elysia-intlayer",
|
|
3
|
+
"version": "9.4.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Manage internationalization i18n in a simple way for Elysia applications.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"intlayer",
|
|
8
|
+
"elysia",
|
|
9
|
+
"backend",
|
|
10
|
+
"i18n",
|
|
11
|
+
"api",
|
|
12
|
+
"application",
|
|
13
|
+
"typescript",
|
|
14
|
+
"javascript",
|
|
15
|
+
"plugin"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://intlayer.org",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/aymericzip/intlayer/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/aymericzip/intlayer.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "Apache-2.0",
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Aymeric PINEAU",
|
|
28
|
+
"url": "https://github.com/aymericzip"
|
|
29
|
+
},
|
|
30
|
+
"contributors": [
|
|
31
|
+
{
|
|
32
|
+
"name": "Aymeric Pineau",
|
|
33
|
+
"email": "ay.pineau@gmail.com",
|
|
34
|
+
"url": "https://github.com/aymericzip"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/types/index.d.ts",
|
|
41
|
+
"require": "./dist/cjs/index.cjs",
|
|
42
|
+
"import": "./dist/esm/index.mjs"
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"main": "dist/cjs/index.cjs",
|
|
47
|
+
"module": "dist/esm/index.mjs",
|
|
48
|
+
"types": "dist/types/index.d.ts",
|
|
49
|
+
"typesVersions": {
|
|
50
|
+
"*": {
|
|
51
|
+
"package.json": [
|
|
52
|
+
"./package.json"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"./dist",
|
|
58
|
+
"./package.json"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "bun --bun tsdown --config tsdown.config.ts",
|
|
62
|
+
"build:ci": "bun --bun tsdown --config tsdown.config.ts",
|
|
63
|
+
"clean": "bun --bun rimraf ./dist .turbo",
|
|
64
|
+
"dev": "bun --bun tsdown --config tsdown.config.ts --watch",
|
|
65
|
+
"format": "bun --bun biome format . --check",
|
|
66
|
+
"format:fix": "bun --bun biome format --write .",
|
|
67
|
+
"lint": "bun --bun biome lint .",
|
|
68
|
+
"lint:fix": "bun --bun biome lint --write .",
|
|
69
|
+
"prepublish": "cp -f ../../README.md ./README.md",
|
|
70
|
+
"publish": "bun publish || true",
|
|
71
|
+
"publish:canary": "bun publish --access public --tag canary || true",
|
|
72
|
+
"publish:latest": "bun publish --access public --tag latest || true",
|
|
73
|
+
"test": "bun --bun vitest run",
|
|
74
|
+
"test:watch": "bun --bun vitest",
|
|
75
|
+
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@intlayer/config": "9.4.0-canary.0",
|
|
79
|
+
"@intlayer/core": "9.4.0-canary.0",
|
|
80
|
+
"@intlayer/engine": "9.4.0-canary.0",
|
|
81
|
+
"@intlayer/types": "9.4.0-canary.0"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@types/node": "26.2.0",
|
|
85
|
+
"@utils/ts-config": "1.0.4",
|
|
86
|
+
"@utils/ts-config-types": "1.0.4",
|
|
87
|
+
"@utils/tsdown-config": "1.0.4",
|
|
88
|
+
"elysia": "1.4.29",
|
|
89
|
+
"rimraf": "6.1.3",
|
|
90
|
+
"tsdown": "0.22.14",
|
|
91
|
+
"typescript": "7.0.2",
|
|
92
|
+
"vitest": "4.1.11"
|
|
93
|
+
},
|
|
94
|
+
"peerDependencies": {
|
|
95
|
+
"elysia": ">=1.0.0"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=14.18"
|
|
99
|
+
},
|
|
100
|
+
"bug": {
|
|
101
|
+
"url": "https://github.com/aymericzip/intlayer/issues"
|
|
102
|
+
}
|
|
103
|
+
}
|