@intlayer/docs 9.5.7 → 9.5.8
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/blog/ar/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/de/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/en/is_intlayer_lighter_than_paraglide.md +288 -0
- package/blog/en-GB/is_intlayer_lighter_than_paraglide.md +285 -0
- package/blog/es/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/fr/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/hi/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/id/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/it/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/ja/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/ko/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/pl/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/pt/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/ru/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/tr/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/uk/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/vi/is_intlayer_lighter_than_paraglide.md +284 -0
- package/blog/zh/is_intlayer_lighter_than_paraglide.md +284 -0
- package/dist/cjs/generated/blog.entry.cjs +20 -0
- package/dist/cjs/generated/blog.entry.cjs.map +1 -1
- package/dist/cjs/generated/docs.entry.cjs +20 -0
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -2
- package/dist/esm/generated/blog.entry.mjs +20 -0
- package/dist/esm/generated/blog.entry.mjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +20 -0
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/types/generated/blog.entry.d.ts +1 -0
- package/dist/types/generated/blog.entry.d.ts.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +1 -0
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/benchmark/nextjs.md +6 -3
- package/docs/ar/benchmark/solid.md +21 -4
- package/docs/ar/benchmark/svelte.md +18 -3
- package/docs/ar/benchmark/tanstack.md +6 -3
- package/docs/ar/benchmark/vue.md +14 -5
- package/docs/ar/cli/index.md +6 -1
- package/docs/ar/cli/upgrade.md +125 -0
- package/docs/de/benchmark/nextjs.md +6 -3
- package/docs/de/benchmark/solid.md +21 -4
- package/docs/de/benchmark/svelte.md +18 -3
- package/docs/de/benchmark/tanstack.md +6 -3
- package/docs/de/benchmark/vue.md +14 -5
- package/docs/de/cli/index.md +6 -1
- package/docs/de/cli/upgrade.md +125 -0
- package/docs/en/agent_skills.md +29 -1
- package/docs/en/benchmark/nextjs.md +6 -3
- package/docs/en/benchmark/solid.md +21 -4
- package/docs/en/benchmark/svelte.md +18 -3
- package/docs/en/benchmark/tanstack.md +7 -4
- package/docs/en/benchmark/vue.md +15 -6
- package/docs/en/cli/index.md +6 -1
- package/docs/en/cli/upgrade.md +125 -0
- package/docs/en-GB/benchmark/nextjs.md +6 -3
- package/docs/en-GB/benchmark/solid.md +21 -4
- package/docs/en-GB/benchmark/svelte.md +18 -3
- package/docs/en-GB/benchmark/tanstack.md +6 -3
- package/docs/en-GB/benchmark/vue.md +15 -6
- package/docs/en-GB/cli/index.md +6 -1
- package/docs/en-GB/cli/upgrade.md +125 -0
- package/docs/es/benchmark/nextjs.md +6 -3
- package/docs/es/benchmark/solid.md +21 -4
- package/docs/es/benchmark/svelte.md +18 -3
- package/docs/es/benchmark/tanstack.md +6 -3
- package/docs/es/benchmark/vue.md +14 -5
- package/docs/es/cli/index.md +6 -1
- package/docs/es/cli/upgrade.md +125 -0
- package/docs/fr/benchmark/nextjs.md +6 -3
- package/docs/fr/benchmark/solid.md +21 -4
- package/docs/fr/benchmark/svelte.md +18 -3
- package/docs/fr/benchmark/tanstack.md +6 -3
- package/docs/fr/benchmark/vue.md +15 -6
- package/docs/fr/cli/index.md +6 -1
- package/docs/fr/cli/upgrade.md +125 -0
- package/docs/hi/benchmark/nextjs.md +6 -3
- package/docs/hi/benchmark/solid.md +21 -4
- package/docs/hi/benchmark/svelte.md +18 -3
- package/docs/hi/benchmark/tanstack.md +6 -3
- package/docs/hi/benchmark/vue.md +15 -6
- package/docs/hi/cli/index.md +6 -1
- package/docs/hi/cli/upgrade.md +125 -0
- package/docs/id/benchmark/nextjs.md +6 -3
- package/docs/id/benchmark/solid.md +21 -4
- package/docs/id/benchmark/svelte.md +18 -3
- package/docs/id/benchmark/tanstack.md +6 -3
- package/docs/id/benchmark/vue.md +15 -6
- package/docs/id/cli/index.md +6 -1
- package/docs/id/cli/upgrade.md +125 -0
- package/docs/it/benchmark/nextjs.md +6 -3
- package/docs/it/benchmark/solid.md +21 -4
- package/docs/it/benchmark/svelte.md +18 -3
- package/docs/it/benchmark/tanstack.md +6 -3
- package/docs/it/benchmark/vue.md +14 -5
- package/docs/it/cli/index.md +6 -1
- package/docs/it/cli/upgrade.md +125 -0
- package/docs/ja/benchmark/nextjs.md +6 -3
- package/docs/ja/benchmark/solid.md +21 -4
- package/docs/ja/benchmark/svelte.md +18 -3
- package/docs/ja/benchmark/tanstack.md +5 -2
- package/docs/ja/benchmark/vue.md +14 -5
- package/docs/ja/cli/index.md +6 -1
- package/docs/ja/cli/upgrade.md +125 -0
- package/docs/ko/benchmark/nextjs.md +6 -3
- package/docs/ko/benchmark/solid.md +21 -4
- package/docs/ko/benchmark/svelte.md +18 -3
- package/docs/ko/benchmark/tanstack.md +6 -3
- package/docs/ko/benchmark/vue.md +15 -6
- package/docs/ko/cli/index.md +6 -1
- package/docs/ko/cli/upgrade.md +126 -0
- package/docs/pl/benchmark/nextjs.md +6 -3
- package/docs/pl/benchmark/solid.md +21 -4
- package/docs/pl/benchmark/svelte.md +18 -3
- package/docs/pl/benchmark/tanstack.md +6 -3
- package/docs/pl/benchmark/vue.md +15 -6
- package/docs/pl/cli/index.md +6 -1
- package/docs/pl/cli/upgrade.md +125 -0
- package/docs/pt/benchmark/nextjs.md +6 -3
- package/docs/pt/benchmark/solid.md +21 -4
- package/docs/pt/benchmark/svelte.md +18 -3
- package/docs/pt/benchmark/tanstack.md +6 -3
- package/docs/pt/benchmark/vue.md +14 -5
- package/docs/pt/cli/index.md +6 -1
- package/docs/pt/cli/upgrade.md +125 -0
- package/docs/ru/benchmark/nextjs.md +6 -3
- package/docs/ru/benchmark/solid.md +21 -4
- package/docs/ru/benchmark/svelte.md +18 -3
- package/docs/ru/benchmark/tanstack.md +6 -3
- package/docs/ru/benchmark/vue.md +15 -6
- package/docs/ru/cli/index.md +6 -1
- package/docs/ru/cli/upgrade.md +125 -0
- package/docs/tr/benchmark/nextjs.md +6 -3
- package/docs/tr/benchmark/solid.md +21 -4
- package/docs/tr/benchmark/svelte.md +18 -3
- package/docs/tr/benchmark/tanstack.md +6 -3
- package/docs/tr/benchmark/vue.md +15 -6
- package/docs/tr/cli/index.md +6 -1
- package/docs/tr/cli/upgrade.md +125 -0
- package/docs/uk/benchmark/nextjs.md +6 -3
- package/docs/uk/benchmark/solid.md +21 -4
- package/docs/uk/benchmark/svelte.md +18 -3
- package/docs/uk/benchmark/tanstack.md +6 -3
- package/docs/uk/benchmark/vue.md +15 -6
- package/docs/uk/cli/index.md +6 -1
- package/docs/uk/cli/upgrade.md +125 -0
- package/docs/vi/benchmark/nextjs.md +6 -3
- package/docs/vi/benchmark/solid.md +21 -4
- package/docs/vi/benchmark/svelte.md +18 -3
- package/docs/vi/benchmark/tanstack.md +6 -3
- package/docs/vi/benchmark/vue.md +15 -6
- package/docs/vi/cli/index.md +6 -1
- package/docs/vi/cli/upgrade.md +125 -0
- package/docs/zh/benchmark/nextjs.md +6 -3
- package/docs/zh/benchmark/solid.md +21 -4
- package/docs/zh/benchmark/svelte.md +18 -3
- package/docs/zh/benchmark/tanstack.md +5 -2
- package/docs/zh/benchmark/vue.md +15 -6
- package/docs/zh/cli/index.md +6 -1
- package/docs/zh/cli/upgrade.md +125 -0
- package/package.json +6 -6
- package/src/generated/blog.entry.ts +20 -0
- package/src/generated/docs.entry.ts +20 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-09-23
|
|
3
|
+
updatedAt: 2026-09-23
|
|
4
|
+
title: Intlayer는 Paraglide보다 가벼운가요?
|
|
5
|
+
description: Paraglide는 코드가 저장소 내부로 생성되기 때문에 i18n 벤치마크에서 거의 무료처럼 보입니다. 그 용량이 실제로 어디로 가는지, 노드별 로케일 조회가 성능을 저하시키는 이유, 그리고 Intlayer의 동적 로딩이 모든 언어 대신 단 하나의 로케일만 번들에 포함시키는 방식을 살펴봅니다.
|
|
6
|
+
keywords:
|
|
7
|
+
- Paraglide
|
|
8
|
+
- Intlayer
|
|
9
|
+
- 국제화
|
|
10
|
+
- i18n
|
|
11
|
+
- Bundle size
|
|
12
|
+
- Tree shaking
|
|
13
|
+
- Benchmark
|
|
14
|
+
- Blog
|
|
15
|
+
slugs:
|
|
16
|
+
- blog
|
|
17
|
+
- is-intlayer-lighter-than-paraglide
|
|
18
|
+
author: aymericzip
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Intlayer는 Paraglide보다 가벼운가요?
|
|
22
|
+
|
|
23
|
+
네, 그렇습니다.
|
|
24
|
+
|
|
25
|
+
`Paraglide`는 가장 가벼운 i18n 솔루션으로 널리 알려져 있으며, 얼핏 보면 [벤치마크](https://intlayer.org/ko/doc/benchmark/tanstack) 결과도 이에 동의하는 것처럼 보입니다. 라이브러리 크기가 거의 0에 가깝기 때문입니다. 하지만 라이브러리 크기가 0이라고 해서 브라우저로 전송되는 바이트가 0이라는 의미는 아닙니다. 그 바이트들이 해당 지표가 측정하지 않는 다른 위치에 존재한다는 뜻일 뿐입니다.
|
|
26
|
+
|
|
27
|
+
<TOC/>
|
|
28
|
+
|
|
29
|
+
## 핵심 요약
|
|
30
|
+
|
|
31
|
+
**라이브러리 크기가 사라진 것이 아니라 숨겨진 것입니다:**
|
|
32
|
+
|
|
33
|
+
Paraglide는 런타임과 메시지 함수를 사용자의 코드베이스 내부에 생성합니다. 이 코드는 브라우저로 전송되지만, 라이브러리가 아닌 _사용자의_ 코드로 집계됩니다.
|
|
34
|
+
|
|
35
|
+
**Provider가 없다고 해서 공짜 이득은 아닙니다:**
|
|
36
|
+
|
|
37
|
+
모든 `m.my_key()` 호출은 컨텍스트에서 한 번만 읽는 대신, 렌더링되는 노드마다 독립적으로 쿠키나 스토리지를 직접 읽어 로케일을 결정합니다.
|
|
38
|
+
|
|
39
|
+
**동적 로딩(Dynamic Loading) 부재:**
|
|
40
|
+
|
|
41
|
+
Paraglide는 메시지의 모든 로케일을 클라이언트 번들로 가져옵니다. 반면 Intlayer에서 `importMode: 'dynamic'` 또는 `'fetch'`를 사용하면 현재 렌더링 중인 단 하나의 로케일만 로드합니다.
|
|
42
|
+
|
|
43
|
+
**Tree Shaking이 보장되지 않습니다:**
|
|
44
|
+
|
|
45
|
+
일부 벤치마크 환경에서는 Paraglide가 홍보하는 Tree Shaking이 적용되지 않았습니다. 자체 번들을 반드시 확인해 보시기 바랍니다.
|
|
46
|
+
|
|
47
|
+
## Paraglide의 용량은 실제로 어디로 가는가?
|
|
48
|
+
|
|
49
|
+
벤치마크 보고서의 "라이브러리 크기" 지표는 콘텐츠가 추가되기 전, 빈 컴포넌트에서 각 i18n 라이브러리의 Provider와 훅(hook) 용량만을 측정합니다.
|
|
50
|
+
|
|
51
|
+
| 라이브러리 (TanStack Start) | 라이브러리 크기 (gz) | 라이브러리 크기 (min) |
|
|
52
|
+
| ----------------------------- | -------------------- | --------------------- |
|
|
53
|
+
| `@inlang/paraglide-js@2.15.1` | 1.8 KB | 4.5 KB |
|
|
54
|
+
| `react-intlayer@9.5.1` | 5.0 KB | 15.2 KB |
|
|
55
|
+
|
|
56
|
+
이 수치만 따로 떼어놓고 보면 Paraglide가 이기는 것처럼 보입니다. 하지만 Paraglide는 컴파일러입니다. `messages/*.json` 파일을 읽어 프로젝트 저장소에 `paraglide/` 폴더를 생성하고, 여기에 `runtime.js`(로케일 감지, 쿠키 및 스토리지 전략, URL 현지화)와 메시지당 하나의 JavaScript 함수를 생성합니다.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
src/paraglide/
|
|
60
|
+
├── runtime.js # 로케일 감지, 전략, URL 헬퍼
|
|
61
|
+
├── server.js
|
|
62
|
+
├── messages.js # 모든 메시지 재내보내기
|
|
63
|
+
└── messages/
|
|
64
|
+
├── _index.js
|
|
65
|
+
├── en.js
|
|
66
|
+
└── fr.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
이 코드는 사용자의 `src/` 폴더에 위치하고 상대 경로로 가져오기 때문에, 번들러는 이를 `node_modules` 패키지가 아니라 애플리케이션 자체 코드로 분류합니다. 결과적으로 라이브러리 크기 열에는 거의 아무것도 나타나지 않지만, 동일한 로직이 페이지 번들에 그대로 포함되어 브라우저로 전송됩니다.
|
|
70
|
+
|
|
71
|
+
코드 생성 자체가 나쁜 접근법은 아닙니다. 생성된 런타임에는 설정에 필요한 로직(접두사 전략, 쿠키 vs 로컬 스토리지 등)만 포함됩니다. Intlayer는 빌드 시점에 환경 변수를 주입하여 설정에서 사용하지 않는 분기를 번들러가 자동으로 제거하는 방식으로 동일한 결과를 달성합니다. 두 방식 모두 `i18next`나 `next-intl`보다 3배에서 10배 더 가볍습니다.
|
|
72
|
+
|
|
73
|
+
따라서 공정한 비교 기준은 라이브러리 크기가 아닙니다. **페이지당 실제로 전송되는 JavaScript의 양**입니다.
|
|
74
|
+
|
|
75
|
+
## 실측된 페이지 용량
|
|
76
|
+
|
|
77
|
+
TanStack Start 앱, 10개 페이지, `en` 및 `fr` 라우트 측정, gzip 압축:
|
|
78
|
+
|
|
79
|
+
| 구성 | 페이지 JS 평균 (gz) | 베이스 대비 | 로케일 누수 | 타 페이지 누수 |
|
|
80
|
+
| ---------------------------------- | ------------------- | ----------- | ----------- | -------------- |
|
|
81
|
+
| 베이스 (i18n 없음) | 111.0 KB | - | 0.0% | 0.0% |
|
|
82
|
+
| `paraglide` (모든 전략) | 125.1 KB | +14.1 KB | 49.7% | 0.0% |
|
|
83
|
+
| `intlayer` (`importMode: static`) | 125.8 KB | +14.8 KB | 50.0% | 0.0% |
|
|
84
|
+
| `intlayer` (`importMode: dynamic`) | **118.6 KB** | **+7.6 KB** | **0.0%** | **0.0%** |
|
|
85
|
+
|
|
86
|
+
Next.js 16 App Router, 동일 애플리케이션:
|
|
87
|
+
|
|
88
|
+
| 구성 | 페이지 JS 평균 (gz) | 베이스 대비 |
|
|
89
|
+
| ------------------ | ------------------- | ----------- |
|
|
90
|
+
| 베이스 (i18n 없음) | 141.0 KB | - |
|
|
91
|
+
| `paraglide-next` | 155.3 KB | +14.3 KB |
|
|
92
|
+
| `next-intlayer` | **141.3 KB** | **+0.3 KB** |
|
|
93
|
+
|
|
94
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
95
|
+
|
|
96
|
+
> 전체 데이터는 [TanStack Start 벤치마크 보고서](https://intlayer.org/ko/doc/benchmark/tanstack)와 [Next.js 벤치마크 보고서](https://intlayer.org/ko/doc/benchmark/nextjs)에서 확인할 수 있습니다. 모든 번들은 [벤치마크 저장소](https://github.com/intlayer-org/benchmark-i18n)에서 직접 분석할 수 있습니다.
|
|
97
|
+
|
|
98
|
+
두 가지 주목할 만한 점이 드러납니다:
|
|
99
|
+
|
|
100
|
+
- `static` 모드에서 Intlayer는 Paraglide와 사실상 동일한 양의 콘텐츠를 전송합니다(125.8 KB vs. 125.1 KB). 이는 자연스러운 결과입니다. 둘 다 페이지에서 사용하는 메시지의 모든 로케일을 포함하기 때문입니다.
|
|
101
|
+
- Paraglide는 동적 모드를 지원하지 않으므로 어떤 전략을 사용하든 125.1 KB로 유지됩니다. 위의 표에 있는 모든 행이 정적 방식에 해당합니다.
|
|
102
|
+
|
|
103
|
+
## Provider 부재: 좋아 보이지만 실상은 그렇지 않은 설계
|
|
104
|
+
|
|
105
|
+
Paraglide에는 Provider가 없습니다. 메시지를 가져와 바로 호출하기만 하면 됩니다:
|
|
106
|
+
|
|
107
|
+
```tsx fileName="Hero.tsx"
|
|
108
|
+
import { m } from "../paraglide/messages.js";
|
|
109
|
+
|
|
110
|
+
export const Hero = () => (
|
|
111
|
+
<section>
|
|
112
|
+
<h1>{m.hero_title()}</h1>
|
|
113
|
+
<p>{m.hero_description()}</p>
|
|
114
|
+
<button>{m.hero_cta()}</button>
|
|
115
|
+
</section>
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
컨텍스트도, 래퍼도, 훅도 없습니다. 겉보기에는 훨씬 단순해 보입니다. 그러나 로케일 정보는 여전히 어디선가 읽어와야 합니다. 생성된 각 메시지 함수는 대략 다음과 같이 생겼습니다(단순화한 코드):
|
|
120
|
+
|
|
121
|
+
```js fileName="paraglide/messages/_index.js"
|
|
122
|
+
export const hero_title = (inputs = {}, options = {}) => {
|
|
123
|
+
const locale = options.locale ?? getLocale(); // 매 호출마다 실행됨
|
|
124
|
+
|
|
125
|
+
if (locale === "en") return en.hero_title(inputs);
|
|
126
|
+
if (locale === "fr") return fr.hero_title(inputs);
|
|
127
|
+
// ...로케일별 분기
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
그리고 `getLocale()`은 설정된 전략(쿠키, 로컬 스토리지, URL, 기본 로케일)을 순회하며 현재 로케일을 찾습니다. 즉, 렌더링되는 모든 텍스트 노드(`<>{m.my_key()}</>`)가 브라우저의 `document.cookie` 읽기를 포함하여 자체적으로 로케일 확인 로직을 수행합니다. 200개의 번역 문자열이 있는 페이지는 렌더링당 200번의 로케일 조회를 실행하고, 리렌더링마다 이를 반복합니다.
|
|
132
|
+
|
|
133
|
+
반면 Provider 기반 라이브러리는 로케일을 **단 한 번만** 읽어 컨텍스트(또는 시그널, 스토어)에 저장하고, 각 노드는 이미 메모리에 저장된 값을 참조합니다. Provider의 크기는 수백 바이트에 불과합니다. 이를 생략하면 렌더링마다 CPU 오버헤드가 발생하며, 이는 벤치마크 결과에 뚜렷하게 반영됩니다. TanStack Start에서 Paraglide의 페이지 로드 및 언어 전환 속도는 Intlayer에 일관되게 뒤처집니다(페이지 로드 22.1 ms vs 14.6 ms, E2E 반응 속도 4.3 ms vs 3.2 ms).
|
|
134
|
+
|
|
135
|
+
## 개발자 경험 (Developer Experience)
|
|
136
|
+
|
|
137
|
+
Paraglide의 원본 소스는 JSON이지만, 개발자가 JSON을 직접 가져오지는 않습니다. 컴파일러가 생성한 `.js` 파일을 가져옵니다:
|
|
138
|
+
|
|
139
|
+
<Tabs defaultTab="intlayer" group="techno">
|
|
140
|
+
<Tab label="Paraglide" value="paraglide">
|
|
141
|
+
|
|
142
|
+
```json fileName="messages/en.json"
|
|
143
|
+
{
|
|
144
|
+
"hero_title": "Ship your app in every language"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```json fileName="messages/ko.json"
|
|
149
|
+
{
|
|
150
|
+
"hero_title": "모든 언어로 앱을 배포하세요"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```tsx fileName="Hero.tsx"
|
|
155
|
+
// 컴파일러가 JSON을 바탕으로 다시 생성한 이후에만 사용 가능
|
|
156
|
+
import { m } from "../paraglide/messages.js";
|
|
157
|
+
|
|
158
|
+
export const Hero = () => <h1>{m.hero_title()}</h1>;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
</Tab>
|
|
162
|
+
<Tab label="Intlayer" value="intlayer">
|
|
163
|
+
|
|
164
|
+
```ts fileName="Hero.content.ts"
|
|
165
|
+
import { t, type Dictionary } from "intlayer";
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
key: "hero",
|
|
169
|
+
content: {
|
|
170
|
+
title: t({
|
|
171
|
+
ko: "모든 언어로 앱을 배포하세요",
|
|
172
|
+
en: "Ship your app in every language",
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
} satisfies Dictionary;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```tsx fileName="Hero.tsx"
|
|
179
|
+
import { useIntlayer } from "react-intlayer";
|
|
180
|
+
|
|
181
|
+
export const Hero = () => {
|
|
182
|
+
const { title } = useIntlayer("hero");
|
|
183
|
+
|
|
184
|
+
return <h1>{title}</h1>;
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
</Tab>
|
|
189
|
+
</Tabs>
|
|
190
|
+
|
|
191
|
+
이러한 개발 워크플로우에는 단점이 따릅니다:
|
|
192
|
+
|
|
193
|
+
- JSON 파일이 변경될 때마다 import가 정상 인식되거나 타입이 갱신되기 전에 재생성 과정을 거쳐야 합니다.
|
|
194
|
+
- 생성된 `paraglide/` 폴더를 Git에 커밋하거나(텍스트를 변경하는 PR마다 생성 파일 충돌 발생) Git에서 무시해야(타입 체크, 테스트, CI 작업 전에 매번 생성 단계 필수) 합니다.
|
|
195
|
+
- 모든 문자열이 함수 호출로 변환됩니다. 단순한 고정 값으로 충분한 곳을 포함하여 모든 곳에서 상수가 `m.key()`로 바뀝니다.
|
|
196
|
+
|
|
197
|
+
## Tree Shaking: 자체 번들을 점검하세요
|
|
198
|
+
|
|
199
|
+
Paraglide의 핵심 장점은 각 메시지가 독립적인 export이므로 사용되지 않는 메시지가 Tree Shaking으로 제거된다는 점입니다. Svelte + Vite 벤치마크에서는 이 기능이 설명대로 잘 작동했습니다.
|
|
200
|
+
|
|
201
|
+
하지만 다른 환경에서는 그렇지 못했습니다. [Next.js](https://intlayer.org/ko/doc/benchmark/nextjs) 측정에서 Paraglide 페이지는 기본 앱보다 14 KB 무거웠던 반면, `next-intlayer`는 0.3 KB만 추가했습니다. TanStack Start의 이전 테스트에서도 다른 페이지의 메시지가 현재 라우트 번들에 섞여 들어가는 현상이 확인되었습니다.
|
|
202
|
+
|
|
203
|
+
Tree Shaking은 번들러(Turbopack, Rolldown, Rollup), 메시지 import 방식(`import { m }` vs. `import * as m`), 부수 효과(side effect) 분석에 크게 의존합니다. 번들 크기 때문에 Paraglide를 고려하고 있다면, 번들 분석기를 열어 프로젝트에서 실제로 Tree Shaking이 정상 작동하는지 확인하시기 바랍니다.
|
|
204
|
+
|
|
205
|
+
## 동적 로딩 부재
|
|
206
|
+
|
|
207
|
+
이것은 구조적인 한계입니다. Paraglide에는 한 번에 하나의 로케일만 로드할 수 있는 메커니즘이 없습니다. 모든 메시지 함수가 각 언어의 구현을 정적으로 가져오므로 모든 언어가 클라이언트 번들에 고스란히 포함됩니다.
|
|
208
|
+
|
|
209
|
+
2개 언어를 지원할 경우 번역 데이터의 절반이 낭비되며, 이는 위에서 측정한 ~50%의 로케일 누수와 정확히 일치합니다. 10개 언어의 경우 90%, 30개 언어의 경우 97%가 낭비됩니다.
|
|
210
|
+
|
|
211
|
+
이를 동적 로딩으로 바꾼다고 해결되지는 않습니다. 메시지마다 별도의 함수가 존재하므로, 각 함수를 지연 로딩하면 수천 번의 네트워크 요청이 발생하게 됩니다.
|
|
212
|
+
|
|
213
|
+
Intlayer는 전역 또는 딕셔너리별로 유연한 선택권을 제공합니다:
|
|
214
|
+
|
|
215
|
+
```ts fileName="intlayer.config.ts"
|
|
216
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
217
|
+
|
|
218
|
+
const config: IntlayerConfig = {
|
|
219
|
+
internationalization: {
|
|
220
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
221
|
+
defaultLocale: Locales.ENGLISH,
|
|
222
|
+
},
|
|
223
|
+
dictionary: {
|
|
224
|
+
importMode: "dynamic", // 'static' | 'dynamic' | 'fetch'
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export default config;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| `importMode` | 클라이언트로 전송되는 내용 | vs. Paraglide |
|
|
232
|
+
| ------------ | ------------------------------------------ | --------------------------------- |
|
|
233
|
+
| `static` | 페이지에서 사용하는 딕셔너리의 모든 로케일 | 이론적으로 동일한 용량 |
|
|
234
|
+
| `dynamic` | 현재 로케일만 딕셔너리 단위로 지연 로드 | N개 로케일 기준 **N배 더 가벼움** |
|
|
235
|
+
| `fetch` | 현재 로케일만 Live Sync API를 통해 가져옴 | N개 로케일 기준 **N배 더 가벼움** |
|
|
236
|
+
|
|
237
|
+
[빌드 변환](https://intlayer.org/ko/doc/concept/bundle-optimization)과 `importMode: 'static'`을 사용하면, Intlayer는 이론상 Paraglide와 완전히 동일한 크기의 콘텐츠를 전송합니다. `'dynamic'` 또는 `'fetch'` 모드에서는 현재 로케일에 필요한 내용만 가져오므로, N개 언어를 지원하는 애플리케이션의 경우 번역 페이로드가 Paraglide 대비 N분의 1로 줄어듭니다.
|
|
238
|
+
|
|
239
|
+
## Paraglide가 여전히 유용한 경우
|
|
240
|
+
|
|
241
|
+
<AccordionGroup>
|
|
242
|
+
<Accordion header="지원 언어가 적은 Svelte + Vite 환경">
|
|
243
|
+
|
|
244
|
+
스택이 Svelte + Vite 기반이고 2~3개 언어만 지원한다면, Tree Shaking이 정상 작동하여 추가 언어로 인한 오버헤드가 적습니다.
|
|
245
|
+
|
|
246
|
+
</Accordion>
|
|
247
|
+
<Accordion header="기존 inlang 워크플로우를 사용하는 팀">
|
|
248
|
+
|
|
249
|
+
이미 inlang 생태계(Fink, Sherlock, 메시지 포맷 플러그인)를 활용하고 있는 팀이라면 Paraglide와 원활하게 통합할 수 있습니다.
|
|
250
|
+
|
|
251
|
+
</Accordion>
|
|
252
|
+
</AccordionGroup>
|
|
253
|
+
|
|
254
|
+
## 실제 애플리케이션에서 테스트해 보세요
|
|
255
|
+
|
|
256
|
+
무료 [i18n SEO Scanner](https://intlayer.org/i18n-seo-scanner)를 통해 프로덕션 애플리케이션의 번들 크기와 로케일 누수를 확인해 보세요:
|
|
257
|
+
|
|
258
|
+
<ClickToOpenIframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
259
|
+
|
|
260
|
+
Intlayer 설정 방법:
|
|
261
|
+
|
|
262
|
+
```bash packageManager="npm"
|
|
263
|
+
npx intlayer init --interactive
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```bash packageManager="pnpm"
|
|
267
|
+
pnpm dlx intlayer init --interactive
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```bash packageManager="yarn"
|
|
271
|
+
yarn dlx intlayer init --interactive
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```bash packageManager="bun"
|
|
275
|
+
bunx intlayer init --interactive
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 추가 자료
|
|
279
|
+
|
|
280
|
+
- [TanStack Start i18n 벤치마크](https://intlayer.org/ko/doc/benchmark/tanstack)
|
|
281
|
+
- [Next.js i18n 벤치마크](https://intlayer.org/ko/doc/benchmark/nextjs)
|
|
282
|
+
- [번들 최적화 및 `importMode`](https://intlayer.org/ko/doc/concept/bundle-optimization)
|
|
283
|
+
- [React i18n 라이브러리 선택 가이드](https://intlayer.org/ko/blog/how-to-pick-react-i18n-library)
|
|
284
|
+
- [컴파일러 기반 국제화의 장단점](https://intlayer.org/ko/blog/compiler-vs-declarative-i18n)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-09-23
|
|
3
|
+
updatedAt: 2026-09-23
|
|
4
|
+
title: Czy Intlayer jest lżejszy niż Paraglide?
|
|
5
|
+
description: Paraglide wygląda na niemal bezkosztowy w benchmarkach i18n, ponieważ jego kod jest generowany bezpośrednio do Twojego repozytorium. Sprawdź, gdzie naprawdę trafia ta waga, dlaczego odczytywanie locale na każdy węzeł obciąża aplikację i jak dynamiczne ładowanie w Intlayer wysyła tylko jeden język zamiast wszystkich.
|
|
6
|
+
keywords:
|
|
7
|
+
- Paraglide
|
|
8
|
+
- Intlayer
|
|
9
|
+
- Internacjonalizacja
|
|
10
|
+
- i18n
|
|
11
|
+
- Bundle size
|
|
12
|
+
- Tree shaking
|
|
13
|
+
- Benchmark
|
|
14
|
+
- Blog
|
|
15
|
+
slugs:
|
|
16
|
+
- blog
|
|
17
|
+
- is-intlayer-lighter-than-paraglide
|
|
18
|
+
author: aymericzip
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Czy Intlayer jest lżejszy niż Paraglide?
|
|
22
|
+
|
|
23
|
+
Tak.
|
|
24
|
+
|
|
25
|
+
`Paraglide` cieszy się świetną reputacją najlżejszego rozwiązania i18n na rynku, a na pierwszy rzut oka [benchmark](https://intlayer.org/pl/doc/benchmark/tanstack) zdaje się to potwierdzać: rozmiar jego biblioteki jest bliski zeru. Jednak rozmiar biblioteki równy zero wcale nie oznacza zero wysłanych bajtów. Oznacza to jedynie, że bajty znajdują się w miejscu, którego ten wskaźnik nie bada.
|
|
26
|
+
|
|
27
|
+
<TOC/>
|
|
28
|
+
|
|
29
|
+
## Kluczowe wnioski
|
|
30
|
+
|
|
31
|
+
**Rozmiar biblioteki jest ukryty, a nie usunięty:**
|
|
32
|
+
|
|
33
|
+
Paraglide generuje swój runtime i funkcje komunikatów bezpośrednio w Twojej bazie kodu. Ten kod trafia do przeglądarki, ale jest liczony jako _Twój_ kod, a nie kod biblioteki.
|
|
34
|
+
|
|
35
|
+
**Brak providera to pozorny zysk:**
|
|
36
|
+
|
|
37
|
+
Każde wywołanie `m.my_key()` samodzielnie rozpoznaje locale, odczytując plik cookie lub pamięć podręczną dla każdego renderowanego węzła, zamiast pobrać tę wartość raz z kontekstu.
|
|
38
|
+
|
|
39
|
+
**Brak dynamicznego ładowania:**
|
|
40
|
+
|
|
41
|
+
Paraglide importuje wszystkie wersje językowe komunikatu do Twojego bundle klienta. Intlayer z opcją `importMode: 'dynamic'` lub `'fetch'` ładuje wyłącznie aktualnie wyświetlane locale.
|
|
42
|
+
|
|
43
|
+
**Tree shaking nie jest gwarantowany:**
|
|
44
|
+
|
|
45
|
+
W niektórych z naszych benchmarków deklarowany przez Paraglide tree shaking nie przyniósł oczekiwanych rezultatów. Warto zweryfikować to we własnym bundle.
|
|
46
|
+
|
|
47
|
+
## Gdzie podziewa się waga Paraglide?
|
|
48
|
+
|
|
49
|
+
W raportach benchmarków metryka „rozmiar biblioteki” mierzy wielkość providera i hooków każdej biblioteki i18n w pustym komponencie, zanim dodana zostanie jakakolwiek treść.
|
|
50
|
+
|
|
51
|
+
| Biblioteka (TanStack Start) | Rozmiar lib (gz) | Rozmiar lib (min) |
|
|
52
|
+
| ----------------------------- | ---------------- | ----------------- |
|
|
53
|
+
| `@inlang/paraglide-js@2.15.1` | 1.8 KB | 4.5 KB |
|
|
54
|
+
| `react-intlayer@9.5.1` | 5.0 KB | 15.2 KB |
|
|
55
|
+
|
|
56
|
+
W oderwaniu od reszty aplikacji Paraglide wygrywa. Jednak Paraglide jest kompilatorem: odczytuje pliki `messages/*.json` i tworzy w Twoim repozytorium folder `paraglide/`, zawierający plik `runtime.js` (wykrywanie locale, strategie cookies i storage, lokalizacja adresów URL) oraz jedną funkcję JavaScript dla każdego komunikatu.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
src/paraglide/
|
|
60
|
+
├── runtime.js # wykrywanie locale, strategie, pomocniki URL
|
|
61
|
+
├── server.js
|
|
62
|
+
├── messages.js # re-eksportuje wszystkie komunikaty
|
|
63
|
+
└── messages/
|
|
64
|
+
├── _index.js
|
|
65
|
+
├── en.js
|
|
66
|
+
└── fr.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ponieważ ten kod znajduje się w Twoim folderze `src/` i jest importowany za pomocą ścieżki względnej, bundler przypisuje go do Twojej aplikacji, a nie do pakietu w `node_modules`. W kolumnie rozmiaru biblioteki nie pojawia się niemal nic, podczas gdy dokładnie ta sama logika nadal jest dostarczana w bundle Twojej strony.
|
|
70
|
+
|
|
71
|
+
Generowanie kodu samo w sobie nie jest złym pomysłem: wygenerowany runtime zawiera wyłącznie logikę wymaganą przez Twoją konfigurację (strategia prefiksów, cookie vs. local storage itp.). Intlayer osiąga ten sam cel w inny sposób: wstrzykując zmienne środowiskowe na etapie budowania, dzięki czemu bundler odrzuca gałęzie kodu, z których nie korzysta Twoja konfiguracja. Oba podejścia okazują się ostatecznie od 3 do 10 razy lżejsze niż `i18next` czy `next-intl`.
|
|
72
|
+
|
|
73
|
+
Sprawiedliwe porównanie nie dotyczy zatem rozmiaru samej biblioteki. Dotyczy **kodu JavaScript faktycznie przesyłanego na stronę**.
|
|
74
|
+
|
|
75
|
+
## Rzeczywista waga strony
|
|
76
|
+
|
|
77
|
+
Aplikacja TanStack Start, 10 stron, pomiar na trasach `en` oraz `fr`, kompresja gzip:
|
|
78
|
+
|
|
79
|
+
| Konfiguracja | Śr. JS strony (gz) | Ponad bazę | Wyciek locale | Wyciek innych stron |
|
|
80
|
+
| ---------------------------------- | ------------------ | ----------- | ------------- | ------------------- |
|
|
81
|
+
| Baza (bez i18n) | 111.0 KB | - | 0.0% | 0.0% |
|
|
82
|
+
| `paraglide` (dowolna strategia) | 125.1 KB | +14.1 KB | 49.7% | 0.0% |
|
|
83
|
+
| `intlayer` (`importMode: static`) | 125.8 KB | +14.8 KB | 50.0% | 0.0% |
|
|
84
|
+
| `intlayer` (`importMode: dynamic`) | **118.6 KB** | **+7.6 KB** | **0.0%** | **0.0%** |
|
|
85
|
+
|
|
86
|
+
Next.js 16 App Router, ta sama aplikacja:
|
|
87
|
+
|
|
88
|
+
| Konfiguracja | Śr. JS strony (gz) | Ponad bazę |
|
|
89
|
+
| ---------------- | ------------------ | ----------- |
|
|
90
|
+
| Baza (bez i18n) | 141.0 KB | - |
|
|
91
|
+
| `paraglide-next` | 155.3 KB | +14.3 KB |
|
|
92
|
+
| `next-intlayer` | **141.3 KB** | **+0.3 KB** |
|
|
93
|
+
|
|
94
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
95
|
+
|
|
96
|
+
> Kompletne dane zawiera [raport benchmarku TanStack Start](https://intlayer.org/pl/doc/benchmark/tanstack) oraz [raport benchmarku Next.js](https://intlayer.org/pl/doc/benchmark/nextjs). Każdy bundle można szczegółowo przeanalizować w [repozytorium benchmarku](https://github.com/intlayer-org/benchmark-i18n).
|
|
97
|
+
|
|
98
|
+
Uwagę zwracają dwa fakty:
|
|
99
|
+
|
|
100
|
+
- W trybie `static` Intlayer przesyła praktycznie taką samą ilość treści jak Paraglide (125.8 KB vs 125.1 KB). Jest to w pełni zrozumiałe: oba rozwiązania dołączają wszystkie wersje językowe komunikatów używanych na stronie.
|
|
101
|
+
- Paraglide pozostaje przy wadze 125.1 KB niezależnie od wybranej strategii, ponieważ nie oferuje trybu dynamicznego. Każdy wiersz w powyższej tabeli odpowiada ładowaniu statycznemu.
|
|
102
|
+
|
|
103
|
+
## Brak Providera: pozorna zaleta o ukrytym koszcie
|
|
104
|
+
|
|
105
|
+
Paraglide nie wymaga providera. Importujesz komunikat i wywołujesz go:
|
|
106
|
+
|
|
107
|
+
```tsx fileName="Hero.tsx"
|
|
108
|
+
import { m } from "../paraglide/messages.js";
|
|
109
|
+
|
|
110
|
+
export const Hero = () => (
|
|
111
|
+
<section>
|
|
112
|
+
<h1>{m.hero_title()}</h1>
|
|
113
|
+
<p>{m.hero_description()}</p>
|
|
114
|
+
<button>{m.hero_cta()}</button>
|
|
115
|
+
</section>
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Brak kontekstu, brak komponentu otaczającego, brak hooka. Wydaje się to prostsze. Jednak informacja o wybranym języku i tak musi skądś pochodzić. Każda wygenerowana funkcja komunikatu wygląda mniej więcej tak (postać uproszczona):
|
|
120
|
+
|
|
121
|
+
```js fileName="paraglide/messages/_index.js"
|
|
122
|
+
export const hero_title = (inputs = {}, options = {}) => {
|
|
123
|
+
const locale = options.locale ?? getLocale(); // rozwiązywane przy każdym wywołaniu
|
|
124
|
+
|
|
125
|
+
if (locale === "en") return en.hero_title(inputs);
|
|
126
|
+
if (locale === "fr") return fr.hero_title(inputs);
|
|
127
|
+
// ...po jednej gałęzi na każdy język
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Funkcja `getLocale()` sprawdza po kolei skonfigurowane strategie (cookie, local storage, URL, domyślne locale), aby ustalić bieżący język. Oznacza to, że każdy renderowany węzeł tekstowy (`<>{m.my_key()}</>`) uruchamia własny proces rozpoznawania locale, co obejmuje odczyt `document.cookie` w przeglądarce. Strona z 200 przetłumaczonymi ciągami znaków wykonuje tę operację 200 razy podczas jednego renderowania, a następnie powtarza to przy każdym ponownym renderowaniu.
|
|
132
|
+
|
|
133
|
+
Biblioteka oparta na providerze odczytuje locale **dokładnie raz**, zapisuje je w kontekście (albo w sygnale czy store), a każdy węzeł pobiera wartość gotową już w pamięci. Provider kosztuje zaledwie kilkaset bajtów. Rezygnacja z niego kosztuje cykle procesora przy każdym renderze, co wyraźnie widać w wynikach benchmarku: czasy ładowania strony i przełączania języków w Paraglide na TanStack Start wyraźnie ustępują Intlayerowi (22.1 ms vs 14.6 ms przy ładowaniu strony, 4.3 ms vs 3.2 ms w reaktywności E2E).
|
|
134
|
+
|
|
135
|
+
## Doświadczenie programisty (DX)
|
|
136
|
+
|
|
137
|
+
Źródłem prawdy w Paraglide jest format JSON, ale programista nigdy nie importuje plików JSON bezpośrednio. Importuje wygenerowany plik `.js`:
|
|
138
|
+
|
|
139
|
+
<Tabs defaultTab="intlayer" group="techno">
|
|
140
|
+
<Tab label="Paraglide" value="paraglide">
|
|
141
|
+
|
|
142
|
+
```json fileName="messages/en.json"
|
|
143
|
+
{
|
|
144
|
+
"hero_title": "Ship your app in every language"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```json fileName="messages/pl.json"
|
|
149
|
+
{
|
|
150
|
+
"hero_title": "Publikuj swoją aplikację w każdym języku"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```tsx fileName="Hero.tsx"
|
|
155
|
+
// Dostępne dopiero po ponownym wygenerowaniu przez kompilator z pliku JSON
|
|
156
|
+
import { m } from "../paraglide/messages.js";
|
|
157
|
+
|
|
158
|
+
export const Hero = () => <h1>{m.hero_title()}</h1>;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
</Tab>
|
|
162
|
+
<Tab label="Intlayer" value="intlayer">
|
|
163
|
+
|
|
164
|
+
```ts fileName="Hero.content.ts"
|
|
165
|
+
import { t, type Dictionary } from "intlayer";
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
key: "hero",
|
|
169
|
+
content: {
|
|
170
|
+
title: t({
|
|
171
|
+
pl: "Publikuj swoją aplikację w każdym języku",
|
|
172
|
+
en: "Ship your app in every language",
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
} satisfies Dictionary;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```tsx fileName="Hero.tsx"
|
|
179
|
+
import { useIntlayer } from "react-intlayer";
|
|
180
|
+
|
|
181
|
+
export const Hero = () => {
|
|
182
|
+
const { title } = useIntlayer("hero");
|
|
183
|
+
|
|
184
|
+
return <h1>{title}</h1>;
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
</Tab>
|
|
189
|
+
</Tabs>
|
|
190
|
+
|
|
191
|
+
Taki cykl pracy wiąże się z niedogodnościami:
|
|
192
|
+
|
|
193
|
+
- Każda zmiana w pliku JSON wymaga ponownej generacji, zanim import zostanie rozpoznany lub zaktualizują się typy.
|
|
194
|
+
- Wygenerowany folder `paraglide/` musi być zatwierdzany w gicie (co prowadzi do konfliktów scalania w wygenerowanych plikach przy każdym PR zmieniającym teksty) albo ignorowany (co wymaga kroku generowania przed każdym sprawdzaniem typów, testem i zadaniem CI).
|
|
195
|
+
- Każdy ciąg znaków staje się wywołaniem funkcji. Stałe zamieniają się w `m.key()` w całym kodzie, nawet tam, gdzie w zupełności wystarczyłaby zwykła wartość tekstowa.
|
|
196
|
+
|
|
197
|
+
## Tree Shaking: sprawdź swój bundle
|
|
198
|
+
|
|
199
|
+
Główną zaletą promowaną przez Paraglide jest to, że nieużywane komunikaty są usuwane przez tree shaking, ponieważ każdy komunikat stanowi niezależny eksport. W benchmarku Svelte + Vite rozwiązanie to działa zgodnie z zapowiedziami.
|
|
200
|
+
|
|
201
|
+
W innych środowiskach tak się jednak nie stało. W naszych testach [Next.js](https://intlayer.org/pl/doc/benchmark/nextjs) strony Paraglide ważyły o 14 KB więcej niż aplikacja bazowa, podczas gdy `next-intlayer` dodał zaledwie 0.3 KB. Wcześniejsze pomiary na TanStack Start wykazały także, że komunikaty z innych podstron trafiały do bundle bieżącej trasy.
|
|
202
|
+
|
|
203
|
+
Skuteczność tree shakingu zależy od używanego bundlera (Turbopack, Rolldown, Rollup), sposobu importowania komunikatów (`import { m }` vs `import * as m`) oraz analizy efektów ubocznych. Wybierając Paraglide ze względu na rozmiar, warto otworzyć wizualizator bundle i sprawdzić, jak zachowuje się on w Twojej aplikacji.
|
|
204
|
+
|
|
205
|
+
## Brak dynamicznego ładowania
|
|
206
|
+
|
|
207
|
+
Oto fundamentalne ograniczenie architektoniczne. Paraglide nie udostępnia mechanizmu ładowania jednego języka na raz: każda funkcja komunikatu statycznie importuje implementację każdego języka, co sprawia, że wszystkie wersje językowe trafiają do bundle klienta.
|
|
208
|
+
|
|
209
|
+
Przy 2 językach marnuje się połowa przesyłanych danych tłumaczeń, co odpowiada zmierzonemu wyżej ~50% wyciekowi locale. Przy 10 językach marnuje się 90% danych. Przy 30 językach aż 97%.
|
|
210
|
+
|
|
211
|
+
Przejście na dynamiczne ładowanie również nie rozwiązałoby problemu: przy jednej funkcji na każdy komunikat leniwe ładowanie każdej z nich generowałoby tysiące zapytań sieciowych.
|
|
212
|
+
|
|
213
|
+
Intlayer pozwala na elastyczny wybór, globalnie lub na poziomie słownika:
|
|
214
|
+
|
|
215
|
+
```ts fileName="intlayer.config.ts"
|
|
216
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
217
|
+
|
|
218
|
+
const config: IntlayerConfig = {
|
|
219
|
+
internationalization: {
|
|
220
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
221
|
+
defaultLocale: Locales.ENGLISH,
|
|
222
|
+
},
|
|
223
|
+
dictionary: {
|
|
224
|
+
importMode: "dynamic", // 'static' | 'dynamic' | 'fetch'
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export default config;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| `importMode` | Co jest przesyłane do klienta | W porównaniu z Paraglide |
|
|
232
|
+
| ------------ | ------------------------------------------------------ | ---------------------------------- |
|
|
233
|
+
| `static` | Wszystkie języki słowników używanych na stronie | Teoretycznie identyczna wielkość |
|
|
234
|
+
| `dynamic` | Tylko bieżące locale, ładowane na żądanie dla słownika | **N razy lżejsze** przy N językach |
|
|
235
|
+
| `fetch` | Tylko bieżące locale, pobierane przez Live Sync API | **N razy lżejsze** przy N językach |
|
|
236
|
+
|
|
237
|
+
Dzięki [transformacji w trakcie budowy](https://intlayer.org/pl/doc/concept/bundle-optimization) oraz trybowi `importMode: 'static'`, Intlayer ładuje w teorii dokładnie tę samą zawartość co Paraglide. Z kolei przy `'dynamic'` lub `'fetch'` ładuje tylko to, czego potrzebuje aktualny język: w aplikacji obsługującej N języków rozmiar danych tłumaczeń jest N razy mniejszy niż w Paraglide.
|
|
238
|
+
|
|
239
|
+
## Kiedy Paraglide nadal ma sens?
|
|
240
|
+
|
|
241
|
+
<AccordionGroup>
|
|
242
|
+
<Accordion header="Svelte + Vite z niewielką liczbą języków">
|
|
243
|
+
|
|
244
|
+
Jeśli Twoim stosem technologicznym jest Svelte z Vite i obsługujesz dwa lub trzy języki, tree shaking działa zgodnie z oczekiwaniami, a narzut dodatkowych języków pozostaje niewielki.
|
|
245
|
+
|
|
246
|
+
</Accordion>
|
|
247
|
+
<Accordion header="Istniejący przepływ pracy oparty na inlang">
|
|
248
|
+
|
|
249
|
+
Jeśli Twój zespół aktywnie korzysta z ekosystemu inlang (Fink, Sherlock, wtyczki formatów komunikatów), Paraglide integruje się z nim natywnie.
|
|
250
|
+
|
|
251
|
+
</Accordion>
|
|
252
|
+
</AccordionGroup>
|
|
253
|
+
|
|
254
|
+
## Przetestuj na własnej aplikacji
|
|
255
|
+
|
|
256
|
+
Sprawdź wagę transferu i wycieki locale w swojej działającej aplikacji za pomocą bezpłatnego narzędzia [i18n SEO Scanner](https://intlayer.org/i18n-seo-scanner):
|
|
257
|
+
|
|
258
|
+
<ClickToOpenIframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
259
|
+
|
|
260
|
+
Aby skonfigurować Intlayer:
|
|
261
|
+
|
|
262
|
+
```bash packageManager="npm"
|
|
263
|
+
npx intlayer init --interactive
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```bash packageManager="pnpm"
|
|
267
|
+
pnpm dlx intlayer init --interactive
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```bash packageManager="yarn"
|
|
271
|
+
yarn dlx intlayer init --interactive
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```bash packageManager="bun"
|
|
275
|
+
bunx intlayer init --interactive
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Przydatne materiały
|
|
279
|
+
|
|
280
|
+
- [Benchmark i18n TanStack Start](https://intlayer.org/pl/doc/benchmark/tanstack)
|
|
281
|
+
- [Benchmark i18n Next.js](https://intlayer.org/pl/doc/benchmark/nextjs)
|
|
282
|
+
- [Optymalizacja bundle i `importMode`](https://intlayer.org/pl/doc/concept/bundle-optimization)
|
|
283
|
+
- [Jak wybrać bibliotekę i18n dla React](https://intlayer.org/pl/blog/how-to-pick-react-i18n-library)
|
|
284
|
+
- [Zalety i wady internacjonalizacji opartej na kompilatorze](https://intlayer.org/pl/blog/compiler-vs-declarative-i18n)
|