@intlayer/docs 8.7.0-canary.0 → 8.7.0-canary.1
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/i18n_using_next-i18next.md +1 -1
- package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/de/i18n_using_next-i18next.md +1 -1
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en/i18n_using_next-i18next.md +1 -1
- package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en-GB/i18n_using_next-i18next.md +1 -1
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/es/i18n_using_next-i18next.md +1 -1
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/fr/i18n_using_next-i18next.md +1 -1
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/hi/i18n_using_next-i18next.md +1 -1
- package/blog/id/i18n_using_next-i18next.md +1 -1
- package/blog/id/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/it/i18n_using_next-i18next.md +1 -1
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ja/i18n_using_next-i18next.md +1 -1
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ko/i18n_using_next-i18next.md +1 -1
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/pl/i18n_using_next-i18next.md +1 -1
- package/blog/pl/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/pt/i18n_using_next-i18next.md +1 -1
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ru/i18n_using_next-i18next.md +1 -1
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/tr/i18n_using_next-i18next.md +1 -1
- package/blog/uk/i18n_using_next-i18next.md +1 -1
- package/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/vi/i18n_using_next-i18next.md +1 -1
- package/blog/vi/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/zh/i18n_using_next-i18next.md +1 -1
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/docs/ar/bundle_optimization.md +454 -0
- package/docs/ar/intlayer_with_next-i18next.md +1 -1
- package/docs/ar/intlayer_with_next-intl.md +1 -1
- package/docs/ar/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ar/intlayer_with_tanstack.md +45 -68
- package/docs/bn/bundle_optimization.md +454 -0
- package/docs/cs/bundle_optimization.md +454 -0
- package/docs/de/bundle_optimization.md +454 -0
- package/docs/de/intlayer_with_next-i18next.md +1 -1
- package/docs/de/intlayer_with_next-intl.md +1 -1
- package/docs/de/intlayer_with_tanstack+solid.md +24 -5
- package/docs/de/intlayer_with_tanstack.md +45 -68
- package/docs/en/bundle_optimization.md +36 -8
- package/docs/en/intlayer_with_next-i18next.md +1 -1
- package/docs/en/intlayer_with_next-intl.md +1 -1
- package/docs/en/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en/intlayer_with_tanstack.md +45 -68
- package/docs/en-GB/bundle_optimization.md +454 -0
- package/docs/en-GB/intlayer_with_next-i18next.md +1 -1
- package/docs/en-GB/intlayer_with_next-intl.md +1 -1
- package/docs/en-GB/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en-GB/intlayer_with_tanstack.md +47 -70
- package/docs/es/bundle_optimization.md +454 -0
- package/docs/es/intlayer_with_next-i18next.md +1 -1
- package/docs/es/intlayer_with_next-intl.md +1 -1
- package/docs/es/intlayer_with_tanstack+solid.md +24 -5
- package/docs/es/intlayer_with_tanstack.md +45 -68
- package/docs/fr/bundle_optimization.md +454 -0
- package/docs/fr/intlayer_with_next-i18next.md +1 -1
- package/docs/fr/intlayer_with_next-intl.md +1 -1
- package/docs/fr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/fr/intlayer_with_tanstack.md +45 -68
- package/docs/hi/bundle_optimization.md +454 -0
- package/docs/hi/intlayer_with_next-i18next.md +1 -1
- package/docs/hi/intlayer_with_next-intl.md +1 -1
- package/docs/hi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/hi/intlayer_with_tanstack.md +45 -68
- package/docs/id/bundle_optimization.md +454 -0
- package/docs/id/intlayer_with_next-i18next.md +1 -1
- package/docs/id/intlayer_with_next-intl.md +1 -1
- package/docs/id/intlayer_with_tanstack+solid.md +24 -5
- package/docs/id/intlayer_with_tanstack.md +45 -68
- package/docs/it/bundle_optimization.md +454 -0
- package/docs/it/intlayer_with_next-i18next.md +1 -1
- package/docs/it/intlayer_with_next-intl.md +1 -1
- package/docs/it/intlayer_with_tanstack+solid.md +24 -5
- package/docs/it/intlayer_with_tanstack.md +45 -68
- package/docs/ja/bundle_optimization.md +454 -0
- package/docs/ja/intlayer_with_next-i18next.md +1 -1
- package/docs/ja/intlayer_with_next-intl.md +1 -1
- package/docs/ja/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ja/intlayer_with_tanstack.md +45 -36
- package/docs/ko/bundle_optimization.md +454 -0
- package/docs/ko/intlayer_with_next-i18next.md +1 -1
- package/docs/ko/intlayer_with_next-intl.md +1 -1
- package/docs/ko/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ko/intlayer_with_tanstack.md +45 -68
- package/docs/nl/bundle_optimization.md +454 -0
- package/docs/pl/bundle_optimization.md +454 -0
- package/docs/pl/intlayer_with_next-i18next.md +1 -1
- package/docs/pl/intlayer_with_next-intl.md +1 -1
- package/docs/pl/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pl/intlayer_with_tanstack.md +45 -68
- package/docs/pt/bundle_optimization.md +454 -0
- package/docs/pt/intlayer_with_next-i18next.md +1 -1
- package/docs/pt/intlayer_with_next-intl.md +1 -1
- package/docs/pt/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pt/intlayer_with_tanstack.md +45 -68
- package/docs/ru/bundle_optimization.md +454 -0
- package/docs/ru/intlayer_with_next-i18next.md +1 -1
- package/docs/ru/intlayer_with_next-intl.md +1 -1
- package/docs/ru/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ru/intlayer_with_tanstack.md +45 -68
- package/docs/tr/bundle_optimization.md +454 -0
- package/docs/tr/intlayer_with_next-i18next.md +1 -1
- package/docs/tr/intlayer_with_next-intl.md +1 -1
- package/docs/tr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/tr/intlayer_with_tanstack.md +45 -68
- package/docs/uk/bundle_optimization.md +454 -0
- package/docs/uk/intlayer_with_next-i18next.md +1 -1
- package/docs/uk/intlayer_with_next-intl.md +1 -1
- package/docs/uk/intlayer_with_tanstack+solid.md +24 -5
- package/docs/uk/intlayer_with_tanstack.md +45 -68
- package/docs/ur/bundle_optimization.md +454 -0
- package/docs/vi/bundle_optimization.md +454 -0
- package/docs/vi/intlayer_with_next-i18next.md +1 -1
- package/docs/vi/intlayer_with_next-intl.md +1 -1
- package/docs/vi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/vi/intlayer_with_tanstack.md +45 -68
- package/docs/zh/bundle_optimization.md +454 -0
- package/docs/zh/intlayer_with_next-i18next.md +1 -1
- package/docs/zh/intlayer_with_next-intl.md +1 -1
- package/docs/zh/intlayer_with_tanstack+solid.md +24 -5
- package/docs/zh/intlayer_with_tanstack.md +45 -68
- package/package.json +7 -7
|
@@ -228,9 +228,7 @@ function RootDocument({ children }: { children: ReactNode }) {
|
|
|
228
228
|
<HeadContent />
|
|
229
229
|
</head>
|
|
230
230
|
<body>
|
|
231
|
-
<IntlayerProvider locale={locale}>
|
|
232
|
-
{children}
|
|
233
|
-
</IntlayerProvider>
|
|
231
|
+
<IntlayerProvider locale={locale}>{children}</IntlayerProvider>
|
|
234
232
|
<Scripts />
|
|
235
233
|
</body>
|
|
236
234
|
</html>
|
|
@@ -333,30 +331,20 @@ import { getPrefix } from "intlayer";
|
|
|
333
331
|
|
|
334
332
|
export const LOCALE_ROUTE = "{-$locale}" as const;
|
|
335
333
|
|
|
336
|
-
|
|
337
|
-
export type RemoveLocaleParam<T> = T extends string
|
|
338
|
-
? RemoveLocaleFromString<T>
|
|
339
|
-
: T;
|
|
334
|
+
export type To = StripLocalePrefix<LinkComponentProps["to"]>;
|
|
340
335
|
|
|
341
|
-
export type
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
export type StripLocalePrefix<T extends string | undefined> = T extends
|
|
337
|
+
| `/${typeof LOCALE_ROUTE}/`
|
|
338
|
+
| `/${typeof LOCALE_ROUTE}`
|
|
339
|
+
? "/"
|
|
340
|
+
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
341
|
+
? `/${Rest}`
|
|
342
|
+
: T;
|
|
345
343
|
|
|
346
344
|
type LocalizedLinkProps = {
|
|
347
345
|
to?: To;
|
|
348
346
|
} & Omit<LinkComponentProps, "to">;
|
|
349
347
|
|
|
350
|
-
// ヘルパー
|
|
351
|
-
type RemoveAll<
|
|
352
|
-
S extends string,
|
|
353
|
-
Sub extends string,
|
|
354
|
-
> = S extends `${infer H}${Sub}${infer T}` ? RemoveAll<`${H}${T}`, Sub> : S;
|
|
355
|
-
|
|
356
|
-
type RemoveLocaleFromString<S extends string> = CollapseDoubleSlashes<
|
|
357
|
-
RemoveAll<S, typeof LOCALE_ROUTE>
|
|
358
|
-
>;
|
|
359
|
-
|
|
360
348
|
export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {
|
|
361
349
|
const { locale } = useLocale();
|
|
362
350
|
const { localePrefix } = getPrefix(locale);
|
|
@@ -385,26 +373,26 @@ export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {
|
|
|
385
373
|
import { useNavigate } from "@tanstack/react-router";
|
|
386
374
|
import { getPrefix } from "intlayer";
|
|
387
375
|
import { useLocale } from "react-intlayer";
|
|
388
|
-
import {
|
|
376
|
+
import type { StripLocalePrefix } from "@/components/localized-link";
|
|
389
377
|
import type { FileRouteTypes } from "@/routeTree.gen";
|
|
390
378
|
|
|
391
|
-
type
|
|
392
|
-
|
|
393
|
-
| `/${typeof LOCALE_ROUTE}/`
|
|
394
|
-
? "/"
|
|
395
|
-
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
396
|
-
? `/${Rest}`
|
|
397
|
-
: never;
|
|
379
|
+
type NavigateFn = ReturnType<typeof useNavigate>;
|
|
380
|
+
type BaseNavigateOptions = Parameters<NavigateFn>[0];
|
|
398
381
|
|
|
399
382
|
type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;
|
|
400
383
|
|
|
401
|
-
type
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
384
|
+
export type LocalizedNavigateOptions = Omit<
|
|
385
|
+
BaseNavigateOptions,
|
|
386
|
+
"to" | "params"
|
|
387
|
+
> & {
|
|
388
|
+
to: LocalizedTo;
|
|
389
|
+
params?: Omit<NonNullable<BaseNavigateOptions["params"]>, "locale">;
|
|
406
390
|
};
|
|
407
391
|
|
|
392
|
+
type LocalizedNavigate = (
|
|
393
|
+
options: LocalizedNavigateOptions
|
|
394
|
+
) => ReturnType<NavigateFn>;
|
|
395
|
+
|
|
408
396
|
export const useLocalizedNavigate = () => {
|
|
409
397
|
const navigate = useNavigate();
|
|
410
398
|
|
|
@@ -642,12 +630,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
642
630
|
component: RouteComponent,
|
|
643
631
|
head: ({ params }) => {
|
|
644
632
|
const { locale } = params;
|
|
645
|
-
const
|
|
633
|
+
const path = "/"; // The path for this route
|
|
634
|
+
|
|
635
|
+
const metaContent = getIntlayer("app", locale);
|
|
646
636
|
|
|
647
637
|
return {
|
|
638
|
+
links: [
|
|
639
|
+
// Canonical link: Points to the current localized page
|
|
640
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
641
|
+
|
|
642
|
+
// Hreflang: Tell Google about all localized versions
|
|
643
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
644
|
+
rel: "alternate",
|
|
645
|
+
hrefLang: mapLocale,
|
|
646
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
647
|
+
})),
|
|
648
|
+
|
|
649
|
+
// x-default: For users in unmatched languages
|
|
650
|
+
// Define the default fallback locale (usually your primary language)
|
|
651
|
+
{
|
|
652
|
+
rel: "alternate",
|
|
653
|
+
hrefLang: "x-default",
|
|
654
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
655
|
+
},
|
|
656
|
+
],
|
|
648
657
|
meta: [
|
|
649
658
|
{ title: metaContent.title },
|
|
650
|
-
{
|
|
659
|
+
{ name: "description", content: metaContent.meta.description },
|
|
651
660
|
],
|
|
652
661
|
};
|
|
653
662
|
},
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2025-11-25
|
|
3
|
+
updatedAt: 2026-04-08
|
|
4
|
+
title: i18n 번들 크기 최적화 및 성능
|
|
5
|
+
description: 국제화(i18n) 콘텐츠를 최적화하여 애플리케이션 번들 크기를 줄이세요. Intlayer를 통해 사전의 트리 쉐이킹(tree shaking) 및 지연 로딩(lazy loading)을 활용하는 방법을 알아보세요.
|
|
6
|
+
keywords:
|
|
7
|
+
- 번들 최적화
|
|
8
|
+
- 콘텐츠 자동화
|
|
9
|
+
- 동적 콘텐츠
|
|
10
|
+
- Intlayer
|
|
11
|
+
- Next.js
|
|
12
|
+
- JavaScript
|
|
13
|
+
- React
|
|
14
|
+
slugs:
|
|
15
|
+
- doc
|
|
16
|
+
- concept
|
|
17
|
+
- bundle-optimization
|
|
18
|
+
history:
|
|
19
|
+
- version: 8.7.0
|
|
20
|
+
date: 2026-04-08
|
|
21
|
+
changes: "빌드 구성에 `minify` 및 `purge` 옵션 추가"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# i18n 번들 크기 최적화 및 성능
|
|
25
|
+
|
|
26
|
+
JSON 파일에 의존하는 기존 i18n 솔루션에서 가장 흔한 과제 중 하나는 콘텐츠 크기를 관리하는 것입니다. 개발자가 수동으로 콘텐츠를 네임스페이스로 분리하지 않으면, 사용자는 단 한 페이지를 보기 위해 모든 페이지와 모든 언어의 번역을 다운로드해야 할 수도 있습니다.
|
|
27
|
+
|
|
28
|
+
예를 들어, 10개 언어로 번역된 10페이지 분량의 애플리케이션의 경우, 사용자는 **한 페이지**(현재 언어의 현재 페이지)만 필요함에도 불구하고 100페이지 분량의 콘텐츠를 다운로드하게 될 수 있습니다. 이는 대역폭 낭비와 로딩 시간 저하로 이어집니다.
|
|
29
|
+
|
|
30
|
+
**Intlayer는 빌드 시 최적화를 통해 이 문제를 해결합니다.** 코드를 분석하여 컴포넌트별로 실제로 사용되는 사전을 감지하고 필요한 콘텐츠만 번들에 재주입합니다.
|
|
31
|
+
|
|
32
|
+
## 목차
|
|
33
|
+
|
|
34
|
+
<TOC />
|
|
35
|
+
|
|
36
|
+
## 번들 스캔
|
|
37
|
+
|
|
38
|
+
번들을 분석하는 것은 "무거운" JSON 파일과 코드 분할 기회를 식별하는 첫 번째 단계입니다. 이러한 도구는 애플리케이션의 컴파일된 코드에 대한 시각적 트리맵(treemap)을 생성하여 어떤 라이브러리가 가장 많은 공간을 차지하고 있는지 정확히 확인할 수 있게 해줍니다.
|
|
39
|
+
|
|
40
|
+
<Tabs>
|
|
41
|
+
<Tab value="vite">
|
|
42
|
+
|
|
43
|
+
### Vite / Rollup
|
|
44
|
+
|
|
45
|
+
Vite는 내부적으로 Rollup을 사용합니다. `rollup-plugin-visualizer` 플러그인은 그래프의 모든 모듈 크기를 보여주는 대화형 HTML 파일을 생성합니다.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install -D rollup-plugin-visualizer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```typescript fileName="vite.config.ts"
|
|
52
|
+
import { defineConfig } from "vite";
|
|
53
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
visualizer({
|
|
58
|
+
open: true, // 브라우저에서 보고서를 자동으로 엽니다
|
|
59
|
+
filename: "stats.html",
|
|
60
|
+
gzipSize: true,
|
|
61
|
+
brotliSize: true,
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
<Tab value="nextjs (turbopack)">
|
|
69
|
+
|
|
70
|
+
### Next.js (Turbopack)
|
|
71
|
+
|
|
72
|
+
App Router 및 Turbopack을 사용하는 프로젝트의 경우, Next.js는 추가 의존성이 필요 없는 내장 실험적 분석기를 제공합니다.
|
|
73
|
+
|
|
74
|
+
```bash packageManager='npm'
|
|
75
|
+
npx next experimental-analyze
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash packageManager='yarn'
|
|
79
|
+
yarn next experimental-analyze
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```bash packageManager='pnpm'
|
|
83
|
+
pnpm next experimental-analyze
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash packageManager='bun'
|
|
87
|
+
bun next experimental-analyze
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</Tab>
|
|
91
|
+
<Tab value="nextjs (Webpack)">
|
|
92
|
+
|
|
93
|
+
### Next.js (Webpack)
|
|
94
|
+
|
|
95
|
+
Next.js에서 기본 Webpack 번들러를 사용하는 경우 공식 번들 분석기를 사용하세요. 빌드 중에 환경 변수를 설정하여 트리거합니다.
|
|
96
|
+
|
|
97
|
+
```bash packageManager='npm'
|
|
98
|
+
npm install -D @next/bundle-analyzer
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```bash packageManager='yarn'
|
|
102
|
+
yarn add -D @next/bundle-analyzer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash packageManager='pnpm'
|
|
106
|
+
pnpm add -D @next/bundle-analyzer
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash packageManager='bun'
|
|
110
|
+
bun add -d @next/bundle-analyzer
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```javascript fileName="next.config.js"
|
|
114
|
+
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|
115
|
+
enabled: process.env.ANALYZE === "true",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
module.exports = withBundleAnalyzer({
|
|
119
|
+
// Next.js 구성
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**사용법:**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
ANALYZE=true npm run build
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</Tab>
|
|
130
|
+
<Tab value="Webpack (CRA / Angular / etc)">
|
|
131
|
+
|
|
132
|
+
### 표준 Webpack
|
|
133
|
+
|
|
134
|
+
Create React App (ejected), Angular 또는 사용자 정의 Webpack 구성의 경우 업계 표준인 `webpack-bundle-analyzer`를 사용하세요.
|
|
135
|
+
|
|
136
|
+
```bash packageManager='npm'
|
|
137
|
+
npm install -D webpack-bundle-analyzer
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash packageManager='yarn'
|
|
141
|
+
yarn add -D webpack-bundle-analyzer
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash packageManager='pnpm'
|
|
145
|
+
pnpm add -D webpack-bundle-analyzer
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```bash packageManager='bun'
|
|
149
|
+
bun add -d webpack-bundle-analyzer
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```typescript fileName="webpack.config.ts
|
|
153
|
+
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
plugins: [
|
|
157
|
+
new BundleAnalyzerPlugin({
|
|
158
|
+
analyzerMode: "static",
|
|
159
|
+
reportFilename: "bundle-analyzer.html",
|
|
160
|
+
openAnalyzer: false,
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
</Tabs>
|
|
168
|
+
|
|
169
|
+
## 작동 방식
|
|
170
|
+
|
|
171
|
+
Intlayer는 **컴포넌트별 접근 방식**을 사용합니다. 전역 JSON 파일과 달리 콘텐츠는 컴포넌트 근처 또는 내부에 정의됩니다. 빌드 프로세스 동안 Intlayer는 다음을 수행합니다.
|
|
172
|
+
|
|
173
|
+
1. 코드를 **분석**하여 `useIntlayer` 호출을 찾습니다.
|
|
174
|
+
2. 해당 사전 콘텐츠를 **빌드**합니다.
|
|
175
|
+
3. 구성에 따라 `useIntlayer` 호출을 최적화된 코드로 **교체**합니다.
|
|
176
|
+
|
|
177
|
+
이를 통해 다음이 보장됩니다.
|
|
178
|
+
|
|
179
|
+
- 컴포넌트가 임포트되지 않으면 해당 콘텐츠는 번들에 포함되지 않습니다(데드 코드 제거).
|
|
180
|
+
- 컴포넌트가 지연 로딩(lazy loading)되면 해당 콘텐츠도 지연 로딩됩니다.
|
|
181
|
+
|
|
182
|
+
## 플랫폼별 설정
|
|
183
|
+
|
|
184
|
+
<Tabs>
|
|
185
|
+
<Tab value="nextjs">
|
|
186
|
+
|
|
187
|
+
### Next.js
|
|
188
|
+
|
|
189
|
+
Next.js는 빌드에 SWC를 사용하므로 변환을 처리하기 위해 `@intlayer/swc` 플러그인이 필요합니다.
|
|
190
|
+
|
|
191
|
+
> 이 플러그인은 Next.js에서 SWC 플러그인이 아직 실험 단계이기 때문에 기본적으로 설치되지 않습니다. 나중에 변경될 수 있습니다.
|
|
192
|
+
|
|
193
|
+
```bash packageManager="npm"
|
|
194
|
+
npm install -D @intlayer/swc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash packageManager="yarn"
|
|
198
|
+
yarn add -D @intlayer/swc
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```bash packageManager="pnpm"
|
|
202
|
+
pnpm add -D @intlayer/swc
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash packageManager="bun"
|
|
206
|
+
bun add -d @intlayer/swc
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
설치되면 Intlayer가 자동으로 플러그인을 감지하여 사용합니다.
|
|
210
|
+
|
|
211
|
+
</Tab>
|
|
212
|
+
<Tab value="vite">
|
|
213
|
+
|
|
214
|
+
### Vite
|
|
215
|
+
|
|
216
|
+
Vite는 `vite-intlayer`의 의존성으로 포함된 `@intlayer/babel` 플러그인을 사용합니다. 최적화는 기본적으로 활성화되어 있습니다. 추가로 할 일은 없습니다.
|
|
217
|
+
|
|
218
|
+
</Tab>
|
|
219
|
+
<Tab value="webpack">
|
|
220
|
+
|
|
221
|
+
### Webpack
|
|
222
|
+
|
|
223
|
+
Webpack에서 Intlayer를 사용한 번들 최적화를 활성화하려면 적절한 Babel (`@intlayer/babel`) 또는 SWC (`@intlayer/swc`) 플러그인을 설치하고 구성해야 합니다.
|
|
224
|
+
|
|
225
|
+
```bash packageManager="npm"
|
|
226
|
+
npm install -D @intlayer/babel
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```bash packageManager="yarn"
|
|
230
|
+
yarn add -D @intlayer/babel
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```bash packageManager="pnpm"
|
|
234
|
+
pnpm add -D @intlayer/babel
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```bash packageManager="bun"
|
|
238
|
+
bun add -d @intlayer/babel
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```typescript fileName="babel.config.js"
|
|
242
|
+
const {
|
|
243
|
+
getOptimizePluginOptions,
|
|
244
|
+
intlayerOptimizeBabelPlugin,
|
|
245
|
+
} = require("@intlayer/babel");
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
plugins: [[intlayerOptimizeBabelPlugin, getOptimizePluginOptions()]],
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
</Tab>
|
|
253
|
+
</Tabs>
|
|
254
|
+
|
|
255
|
+
## 구성
|
|
256
|
+
|
|
257
|
+
`intlayer.config.ts`의 `build` 속성을 통해 Intlayer가 번들을 최적화하는 방법을 제어할 수 있습니다.
|
|
258
|
+
|
|
259
|
+
```typescript fileName="intlayer.config.ts"
|
|
260
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
261
|
+
|
|
262
|
+
const config: IntlayerConfig = {
|
|
263
|
+
internationalization: {
|
|
264
|
+
locales: [Locales.ENGLISH, Locales.FRENCH],
|
|
265
|
+
defaultLocale: Locales.ENGLISH,
|
|
266
|
+
},
|
|
267
|
+
dictionary: {
|
|
268
|
+
importMode: "dynamic",
|
|
269
|
+
},
|
|
270
|
+
build: {
|
|
271
|
+
/**
|
|
272
|
+
* 번들 크기를 줄이기 위해 사전을 압축(minify)합니다.
|
|
273
|
+
*/
|
|
274
|
+
minify: true;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 사전에서 사용되지 않는 키를 제거(purge)합니다.
|
|
278
|
+
*/
|
|
279
|
+
purge: true;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 빌드 시 TypeScript 유형을 확인해야 하는지 여부
|
|
283
|
+
*/
|
|
284
|
+
checkTypes: false;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export default config;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
> 대부분의 경우 `optimize` 옵션을 기본값으로 유지하는 것이 좋습니다.
|
|
292
|
+
|
|
293
|
+
> 자세한 내용은 구성 문서를 참조하세요: [구성](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ko/configuration.md)
|
|
294
|
+
|
|
295
|
+
### 빌드 옵션
|
|
296
|
+
|
|
297
|
+
`build` 구성 개체에서 다음 옵션을 사용할 수 있습니다.
|
|
298
|
+
|
|
299
|
+
| 속성 | 유형 | 기본값 | 설명 |
|
|
300
|
+
| :------------- | :-------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| **`optimize`** | `boolean` | `undefined` | 빌드 최적화 활성화 여부를 제어합니다. `true`인 경우 Intlayer는 사전 호출을 최적화된 주입으로 교체합니다. `false`인 경우 최적화가 비활성화됩니다. 프로덕션 환경에서는 `true`로 설정하는 것이 좋습니다. |
|
|
302
|
+
| **`minify`** | `boolean` | `false` | 번들 크기를 줄이기 위해 사전을 압축할지 여부입니다. |
|
|
303
|
+
| **`purge`** | `boolean` | `false` | 사전에서 사용되지 않는 키를 제거할지 여부입니다. |
|
|
304
|
+
|
|
305
|
+
### 축소(Minification)
|
|
306
|
+
|
|
307
|
+
사전을 축소하면 불필요한 공백, 주석이 제거되고 JSON 콘텐츠의 크기가 줄어듭니다. 이는 대규모 사전에 특히 유용합니다.
|
|
308
|
+
|
|
309
|
+
```typescript fileName="intlayer.config.ts"
|
|
310
|
+
import type { IntlayerConfig } from "intlayer";
|
|
311
|
+
|
|
312
|
+
const config: IntlayerConfig = {
|
|
313
|
+
build: {
|
|
314
|
+
minify: true,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export default config;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
> 참고: `optimize`가 비활성화되어 있거나 비주얼 에디터(Visual Editor)가 활성화된 경우(에디터가 편집을 위해 전체 콘텐츠를 필요로 하므로) 축소는 무시됩니다.
|
|
322
|
+
|
|
323
|
+
### 퍼징(Purging, 제거)
|
|
324
|
+
|
|
325
|
+
퍼징을 사용하면 코드에서 실제로 사용되는 키만 최종 사전 번들에 포함됩니다. 모든 부분에서 사용되지 않는 키가 많은 대규모 사전이 있는 경우 번들 크기를 크게 줄일 수 있습니다.
|
|
326
|
+
|
|
327
|
+
```typescript fileName="intlayer.config.ts"
|
|
328
|
+
import type { IntlayerConfig } from "intlayer";
|
|
329
|
+
|
|
330
|
+
const config: IntlayerConfig = {
|
|
331
|
+
build: {
|
|
332
|
+
purge: true,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default config;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> 참고: `optimize`가 비활성화된 경우 퍼징은 무시됩니다.
|
|
340
|
+
|
|
341
|
+
### 임포트 모드(Import Mode)
|
|
342
|
+
|
|
343
|
+
여러 페이지와 로케일을 포함하는 대규모 애플리케이션의 경우 JSON 파일이 번들 크기의 상당 부분을 차지할 수 있습니다. Intlayer를 사용하면 사전이 로드되는 방식을 제어할 수 있습니다.
|
|
344
|
+
|
|
345
|
+
임포트 모드는 `intlayer.config.ts` 파일의 전역 기본값으로 정의할 수 있습니다.
|
|
346
|
+
|
|
347
|
+
```typescript fileName="intlayer.config.ts"
|
|
348
|
+
import type { IntlayerConfig } from "intlayer";
|
|
349
|
+
|
|
350
|
+
const config: IntlayerConfig = {
|
|
351
|
+
build: {
|
|
352
|
+
minify: true,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export default config;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
또한 `.content.{{ts|tsx|js|jsx|mjs|cjs|json|jsonc|json5}}` 파일의 각 사전에 대해서도 정의할 수 있습니다.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { type Dictionary, t } from "intlayer";
|
|
363
|
+
|
|
364
|
+
const appContent: Dictionary = {
|
|
365
|
+
key: "app",
|
|
366
|
+
importMode: "dynamic", // 기본 임포트 모드 재정의
|
|
367
|
+
content: {
|
|
368
|
+
// ...
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export default appContent;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
| 속성 | 유형 | 기본값 | 설명 |
|
|
376
|
+
| :--------------- | :--------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------- |
|
|
377
|
+
| **`importMode`** | `'static'`, `'dynamic'`, `'fetch'` | `'static'` | **사용 중단 예정**: 대신 `dictionary.importMode`를 사용하세요. 사전 로드 방식을 결정합니다(아래 세부 정보 참조). |
|
|
378
|
+
|
|
379
|
+
`importMode` 설정은 사전 콘텐츠가 컴포넌트에 주입되는 방식을 규정합니다.
|
|
380
|
+
`intlayer.config.ts` 파일의 `dictionary` 개체 아래에서 전역적으로 정의하거나, 특정 사전의 `.content.ts` 파일에서 재정의할 수 있습니다.
|
|
381
|
+
|
|
382
|
+
### 1. 정적 모드 (`default`)
|
|
383
|
+
|
|
384
|
+
정적 모드에서 Intlayer는 `useIntlayer`를 `useDictionary`로 교체하고 사전을 JavaScript 번들에 직접 주입합니다.
|
|
385
|
+
|
|
386
|
+
- **장점:** 즉시 렌더링(동기식), 하이드레이션 중 추가 네트워크 요청 없음.
|
|
387
|
+
- **단점:** 번들에 해당 특정 컴포넌트에서 사용 가능한 **모든** 언어의 번역이 포함됩니다.
|
|
388
|
+
- **최적의 사례:** 싱글 페이지 애플리케이션(SPA).
|
|
389
|
+
|
|
390
|
+
**변환된 코드 예시:**
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// 원래 코드
|
|
394
|
+
const content = useIntlayer("my-key");
|
|
395
|
+
|
|
396
|
+
// 최적화된 코드 (정적)
|
|
397
|
+
const content = useDictionary({
|
|
398
|
+
key: "my-key",
|
|
399
|
+
content: {
|
|
400
|
+
nodeType: "translation",
|
|
401
|
+
translation: {
|
|
402
|
+
en: "My title",
|
|
403
|
+
fr: "Mon titre",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 2. 동적 모드
|
|
410
|
+
|
|
411
|
+
동적 모드에서 Intlayer는 `useIntlayer`를 `useDictionaryAsync`로 교체합니다. 이는 `import()`(Suspense와 유사한 메커니즘)를 사용하여 현재 로케일에 특정한 JSON을 지연 로드합니다.
|
|
412
|
+
|
|
413
|
+
- **장점:** **로케일 수준의 트리 쉐이킹.** 영어 버전을 보는 사용자는 영어 사전만 다운로드합니다. 프랑스어 사전은 로드되지 않습니다.
|
|
414
|
+
- **단점:** 하이드레이션 중 컴포넌트당 네트워크 요청(에셋 페치)을 트리거합니다.
|
|
415
|
+
- **최적의 사례:** 번들 크기가 중요한 텍스트가 많은 페이지, 많은 언어를 지원하는 애플리케이션.
|
|
416
|
+
|
|
417
|
+
**변환된 코드 예시:**
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
// 원래 코드
|
|
421
|
+
const content = useIntlayer("my-key");
|
|
422
|
+
|
|
423
|
+
// 최적화된 코드 (동적)
|
|
424
|
+
const content = useDictionaryAsync({
|
|
425
|
+
en: () =>
|
|
426
|
+
import(".intlayer/dynamic_dictionary/my-key/en.json").then(
|
|
427
|
+
(mod) => mod.default
|
|
428
|
+
),
|
|
429
|
+
fr: () =>
|
|
430
|
+
import(".intlayer/dynamic_dictionary/my-key/fr.json").then(
|
|
431
|
+
(mod) => mod.default
|
|
432
|
+
),
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> `importMode: 'dynamic'`을 사용할 때 단일 페이지에서 `useIntlayer`를 사용하는 컴포넌트가 100개인 경우 브라우저는 100개의 개별 페치를 시도합니다. 이러한 "폭포수(waterfall)" 요청을 피하려면 콘텐츠를 원자(atom) 컴포넌트별로 하나씩 만들기보다 더 적은 수의 `.content` 파일(예: 페이지 섹션당 하나의 사전)로 그룹화하세요.
|
|
437
|
+
|
|
438
|
+
### 3. 페치 모드(Fetch Mode)
|
|
439
|
+
|
|
440
|
+
동적 모드와 유사하게 작동하지만 먼저 Intlayer Live Sync API에서 사전을 가져오려고 시도합니다. API 호출이 실패하거나 콘텐츠가 라이브 업데이트로 표시되지 않은 경우 동적 임포트로 대체됩니다.
|
|
441
|
+
|
|
442
|
+
> 자세한 내용은 CMS 문서를 참조하세요: [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ko/intlayer_CMS.md)
|
|
443
|
+
|
|
444
|
+
> 페치 모드에서는 제거(purge) 및 축소(minification)를 사용할 수 없습니다.
|
|
445
|
+
|
|
446
|
+
## 요약: 정적 vs 동적
|
|
447
|
+
|
|
448
|
+
| 특징 | 정적 모드 | 동적 모드 |
|
|
449
|
+
| :-------------------- | :-------------------------------- | :---------------------------------- |
|
|
450
|
+
| **JS 번들 크기** | 더 큼 (컴포넌트의 모든 언어 포함) | 가장 작음 (코드만 있고 콘텐츠 없음) |
|
|
451
|
+
| **초기 로드** | 즉시 (번들 내에 콘텐츠 있음) | 약간의 지연 (JSON 페치) |
|
|
452
|
+
| **네트워크 요청** | 0개 추가 요청 | 사전당 1개 요청 |
|
|
453
|
+
| **트리 쉐이킹** | 컴포넌트 수준 | 컴포넌트 수준 + 로케일 수준 |
|
|
454
|
+
| **최적의 유스케이스** | UI 컴포넌트, 소규모 앱 | 텍스트가 많은 페이지, 다국어 지원 |
|
|
@@ -256,7 +256,7 @@ export default function LocaleLayout({
|
|
|
256
256
|
params: { locale: string };
|
|
257
257
|
}) {
|
|
258
258
|
const locale: Locale = (locales as readonly string[]).includes(params.locale)
|
|
259
|
-
?
|
|
259
|
+
? params.locale
|
|
260
260
|
: defaultLocale;
|
|
261
261
|
|
|
262
262
|
const dir = isRtl(locale) ? "rtl" : "ltr";
|
|
@@ -103,7 +103,7 @@ async function loadMessages(locale: string) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export default getRequestConfig(async ({ locale }) => {
|
|
106
|
-
if (!locales.includes(locale
|
|
106
|
+
if (!locales.includes(locale)) notFound();
|
|
107
107
|
|
|
108
108
|
return {
|
|
109
109
|
messages: await loadMessages(locale),
|
|
@@ -194,9 +194,7 @@ const RootComponent: ParentComponent = (props) => {
|
|
|
194
194
|
</head>
|
|
195
195
|
<body>
|
|
196
196
|
<IntlayerProvider locale={locale}>
|
|
197
|
-
<Suspense>
|
|
198
|
-
{props.children}
|
|
199
|
-
</Suspense>
|
|
197
|
+
<Suspense>{props.children}</Suspense>
|
|
200
198
|
</IntlayerProvider>
|
|
201
199
|
<Scripts />
|
|
202
200
|
</body>
|
|
@@ -506,12 +504,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
506
504
|
component: RouteComponent,
|
|
507
505
|
head: ({ params }) => {
|
|
508
506
|
const { locale } = params;
|
|
509
|
-
const
|
|
507
|
+
const path = "/"; // The path for this route
|
|
508
|
+
|
|
509
|
+
const metaContent = getIntlayer("app", locale);
|
|
510
510
|
|
|
511
511
|
return {
|
|
512
|
+
links: [
|
|
513
|
+
// Canonical link: Points to the current localized page
|
|
514
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
515
|
+
|
|
516
|
+
// Hreflang: Tell Google about all localized versions
|
|
517
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
518
|
+
rel: "alternate",
|
|
519
|
+
hrefLang: mapLocale,
|
|
520
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
521
|
+
})),
|
|
522
|
+
|
|
523
|
+
// x-default: For users in unmatched languages
|
|
524
|
+
// Define the default fallback locale (usually your primary language)
|
|
525
|
+
{
|
|
526
|
+
rel: "alternate",
|
|
527
|
+
hrefLang: "x-default",
|
|
528
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
529
|
+
},
|
|
530
|
+
],
|
|
512
531
|
meta: [
|
|
513
532
|
{ title: metaContent.title },
|
|
514
|
-
{
|
|
533
|
+
{ name: "description", content: metaContent.meta.description },
|
|
515
534
|
],
|
|
516
535
|
};
|
|
517
536
|
},
|