@agility/create-next-app 1.0.0-beta.2

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.
Files changed (213) hide show
  1. package/.claude/settings.json +7 -0
  2. package/.claude/settings.local.json +24 -0
  3. package/FEATURE_ROADMAP.md +343 -0
  4. package/README.md +205 -0
  5. package/TESTING.md +131 -0
  6. package/bin/create-agility-app.js +48 -0
  7. package/dist/agility/api-keys/generateApiKeys.d.ts +9 -0
  8. package/dist/agility/api-keys/generateApiKeys.d.ts.map +1 -0
  9. package/dist/agility/api-keys/generateApiKeys.js +99 -0
  10. package/dist/agility/api-keys/generateApiKeys.js.map +1 -0
  11. package/dist/agility/api-keys/getApiKeys.d.ts +9 -0
  12. package/dist/agility/api-keys/getApiKeys.d.ts.map +1 -0
  13. package/dist/agility/api-keys/getApiKeys.js +14 -0
  14. package/dist/agility/api-keys/getApiKeys.js.map +1 -0
  15. package/dist/agility/index.d.ts +3 -0
  16. package/dist/agility/index.d.ts.map +1 -0
  17. package/dist/agility/index.js +8 -0
  18. package/dist/agility/index.js.map +1 -0
  19. package/dist/agility/instance/createNewInstance.d.ts +8 -0
  20. package/dist/agility/instance/createNewInstance.d.ts.map +1 -0
  21. package/dist/agility/instance/createNewInstance.js +65 -0
  22. package/dist/agility/instance/createNewInstance.js.map +1 -0
  23. package/dist/agility/instance/getAvailableInstances.d.ts +8 -0
  24. package/dist/agility/instance/getAvailableInstances.d.ts.map +1 -0
  25. package/dist/agility/instance/getAvailableInstances.js +43 -0
  26. package/dist/agility/instance/getAvailableInstances.js.map +1 -0
  27. package/dist/agility/instance/manageInstance.d.ts +9 -0
  28. package/dist/agility/instance/manageInstance.d.ts.map +1 -0
  29. package/dist/agility/instance/manageInstance.js +82 -0
  30. package/dist/agility/instance/manageInstance.js.map +1 -0
  31. package/dist/agility/utils/getMgmtAPIUrl.d.ts +20 -0
  32. package/dist/agility/utils/getMgmtAPIUrl.d.ts.map +1 -0
  33. package/dist/agility/utils/getMgmtAPIUrl.js +61 -0
  34. package/dist/agility/utils/getMgmtAPIUrl.js.map +1 -0
  35. package/dist/auth/api-key/authenticateWithApiKey.d.ts +6 -0
  36. package/dist/auth/api-key/authenticateWithApiKey.d.ts.map +1 -0
  37. package/dist/auth/api-key/authenticateWithApiKey.js +28 -0
  38. package/dist/auth/api-key/authenticateWithApiKey.js.map +1 -0
  39. package/dist/auth/index.d.ts +3 -0
  40. package/dist/auth/index.d.ts.map +1 -0
  41. package/dist/auth/index.js +8 -0
  42. package/dist/auth/index.js.map +1 -0
  43. package/dist/auth/oauth/authenticate.d.ts +6 -0
  44. package/dist/auth/oauth/authenticate.d.ts.map +1 -0
  45. package/dist/auth/oauth/authenticate.js +162 -0
  46. package/dist/auth/oauth/authenticate.js.map +1 -0
  47. package/dist/auth/oauth/constants.d.ts +5 -0
  48. package/dist/auth/oauth/constants.d.ts.map +1 -0
  49. package/dist/auth/oauth/constants.js +9 -0
  50. package/dist/auth/oauth/constants.js.map +1 -0
  51. package/dist/auth/oauth/exchangeCodeForToken.d.ts +7 -0
  52. package/dist/auth/oauth/exchangeCodeForToken.d.ts.map +1 -0
  53. package/dist/auth/oauth/exchangeCodeForToken.js +39 -0
  54. package/dist/auth/oauth/exchangeCodeForToken.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +290 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/promptForMissingOptions.d.ts +8 -0
  60. package/dist/cli/promptForMissingOptions.d.ts.map +1 -0
  61. package/dist/cli/promptForMissingOptions.js +92 -0
  62. package/dist/cli/promptForMissingOptions.js.map +1 -0
  63. package/dist/config/env/createEnvFile.d.ts +6 -0
  64. package/dist/config/env/createEnvFile.d.ts.map +1 -0
  65. package/dist/config/env/createEnvFile.js +31 -0
  66. package/dist/config/env/createEnvFile.js.map +1 -0
  67. package/dist/config/index.d.ts +2 -0
  68. package/dist/config/index.d.ts.map +1 -0
  69. package/dist/config/index.js +6 -0
  70. package/dist/config/index.js.map +1 -0
  71. package/dist/config/mcp/createMcpConfig.d.ts +5 -0
  72. package/dist/config/mcp/createMcpConfig.d.ts.map +1 -0
  73. package/dist/config/mcp/createMcpConfig.js +32 -0
  74. package/dist/config/mcp/createMcpConfig.js.map +1 -0
  75. package/dist/config/packages/installAgilityPackages.d.ts +6 -0
  76. package/dist/config/packages/installAgilityPackages.d.ts.map +1 -0
  77. package/dist/config/packages/installAgilityPackages.js +61 -0
  78. package/dist/config/packages/installAgilityPackages.js.map +1 -0
  79. package/dist/config/setupProject.d.ts +8 -0
  80. package/dist/config/setupProject.d.ts.map +1 -0
  81. package/dist/config/setupProject.js +32 -0
  82. package/dist/config/setupProject.js.map +1 -0
  83. package/dist/create-next-app/createNextApp.d.ts +9 -0
  84. package/dist/create-next-app/createNextApp.d.ts.map +1 -0
  85. package/dist/create-next-app/createNextApp.js +83 -0
  86. package/dist/create-next-app/createNextApp.js.map +1 -0
  87. package/dist/create-next-app/index.d.ts +3 -0
  88. package/dist/create-next-app/index.d.ts.map +1 -0
  89. package/dist/create-next-app/index.js +8 -0
  90. package/dist/create-next-app/index.js.map +1 -0
  91. package/dist/scaffold/components/createPageComponents.d.ts +6 -0
  92. package/dist/scaffold/components/createPageComponents.d.ts.map +1 -0
  93. package/dist/scaffold/components/createPageComponents.js +62 -0
  94. package/dist/scaffold/components/createPageComponents.js.map +1 -0
  95. package/dist/scaffold/containers/createContainers.d.ts +6 -0
  96. package/dist/scaffold/containers/createContainers.d.ts.map +1 -0
  97. package/dist/scaffold/containers/createContainers.js +48 -0
  98. package/dist/scaffold/containers/createContainers.js.map +1 -0
  99. package/dist/scaffold/index.d.ts +2 -0
  100. package/dist/scaffold/index.d.ts.map +1 -0
  101. package/dist/scaffold/index.js +6 -0
  102. package/dist/scaffold/index.js.map +1 -0
  103. package/dist/scaffold/instance/createBlankInstance.d.ts +8 -0
  104. package/dist/scaffold/instance/createBlankInstance.d.ts.map +1 -0
  105. package/dist/scaffold/instance/createBlankInstance.js +51 -0
  106. package/dist/scaffold/instance/createBlankInstance.js.map +1 -0
  107. package/dist/scaffold/models/createContentModels.d.ts +6 -0
  108. package/dist/scaffold/models/createContentModels.d.ts.map +1 -0
  109. package/dist/scaffold/models/createContentModels.js +70 -0
  110. package/dist/scaffold/models/createContentModels.js.map +1 -0
  111. package/dist/templates/copyDirectory.d.ts +5 -0
  112. package/dist/templates/copyDirectory.d.ts.map +1 -0
  113. package/dist/templates/copyDirectory.js +28 -0
  114. package/dist/templates/copyDirectory.js.map +1 -0
  115. package/dist/templates/copyTemplates.d.ts +8 -0
  116. package/dist/templates/copyTemplates.d.ts.map +1 -0
  117. package/dist/templates/copyTemplates.js +58 -0
  118. package/dist/templates/copyTemplates.js.map +1 -0
  119. package/dist/templates/index.d.ts +2 -0
  120. package/dist/templates/index.d.ts.map +1 -0
  121. package/dist/templates/index.js +6 -0
  122. package/dist/templates/index.js.map +1 -0
  123. package/dist/types/index.d.ts +50 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +3 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/dist/utils/git.d.ts +9 -0
  128. package/dist/utils/git.d.ts.map +1 -0
  129. package/dist/utils/git.js +71 -0
  130. package/dist/utils/git.js.map +1 -0
  131. package/dist/utils/validation.d.ts +45 -0
  132. package/dist/utils/validation.d.ts.map +1 -0
  133. package/dist/utils/validation.js +180 -0
  134. package/dist/utils/validation.js.map +1 -0
  135. package/package.json +45 -0
  136. package/src/agility/api-keys/generateApiKeys.ts +100 -0
  137. package/src/agility/api-keys/getApiKeys.ts +13 -0
  138. package/src/agility/index.ts +3 -0
  139. package/src/agility/instance/createNewInstance.ts +67 -0
  140. package/src/agility/instance/getAvailableInstances.ts +49 -0
  141. package/src/agility/instance/manageInstance.ts +90 -0
  142. package/src/agility/utils/getMgmtAPIUrl.ts +68 -0
  143. package/src/auth/api-key/authenticateWithApiKey.ts +24 -0
  144. package/src/auth/index.ts +3 -0
  145. package/src/auth/oauth/authenticate.ts +165 -0
  146. package/src/auth/oauth/constants.ts +6 -0
  147. package/src/auth/oauth/exchangeCodeForToken.ts +43 -0
  148. package/src/cli/index.ts +281 -0
  149. package/src/cli/promptForMissingOptions.ts +104 -0
  150. package/src/config/env/createEnvFile.ts +30 -0
  151. package/src/config/index.ts +2 -0
  152. package/src/config/mcp/createMcpConfig.ts +30 -0
  153. package/src/config/packages/installAgilityPackages.ts +63 -0
  154. package/src/config/setupProject.ts +31 -0
  155. package/src/create-next-app/createNextApp.ts +75 -0
  156. package/src/create-next-app/index.ts +3 -0
  157. package/src/scaffold/components/createPageComponents.ts +74 -0
  158. package/src/scaffold/containers/createContainers.ts +55 -0
  159. package/src/scaffold/index.ts +2 -0
  160. package/src/scaffold/instance/createBlankInstance.ts +55 -0
  161. package/src/scaffold/models/createContentModels.ts +83 -0
  162. package/src/templates/copyDirectory.ts +24 -0
  163. package/src/templates/copyTemplates.ts +57 -0
  164. package/src/templates/index.ts +2 -0
  165. package/src/types/index.ts +55 -0
  166. package/src/utils/git.ts +74 -0
  167. package/src/utils/validation.ts +184 -0
  168. package/templates/.claude/QUICK-START.md +230 -0
  169. package/templates/.claude/README.md +32 -0
  170. package/templates/.claude/settings.json +8 -0
  171. package/templates/BLANK-INSTANCE-SETUP.md +375 -0
  172. package/templates/DEVELOPMENT.md +160 -0
  173. package/templates/EXAMPLE-PROMPTS.md +643 -0
  174. package/templates/PROMPTS.md +410 -0
  175. package/templates/README.md +281 -0
  176. package/templates/agents.md +429 -0
  177. package/templates/app/[locale]/[...slug]/error.tsx +17 -0
  178. package/templates/app/[locale]/[...slug]/not-found.tsx +9 -0
  179. package/templates/app/[locale]/[...slug]/page.tsx +102 -0
  180. package/templates/app/[locale]/layout.tsx +22 -0
  181. package/templates/app/[locale]/page.tsx +12 -0
  182. package/templates/app/api/dynamic-redirect/route.ts +24 -0
  183. package/templates/app/api/preview/exit/route.ts +34 -0
  184. package/templates/app/api/preview/route.ts +63 -0
  185. package/templates/app/api/revalidate/route.ts +118 -0
  186. package/templates/components/agility-components/RichTextArea.tsx +66 -0
  187. package/templates/components/agility-components/index.ts +30 -0
  188. package/templates/components/agility-pages/MainTemplate.tsx +36 -0
  189. package/templates/components/agility-pages/index.ts +11 -0
  190. package/templates/docs/01-agility-cms-overview.md +139 -0
  191. package/templates/docs/02-page-routing.md +251 -0
  192. package/templates/docs/03-creating-components.md +462 -0
  193. package/templates/docs/04-data-fetching.md +484 -0
  194. package/templates/docs/05-containers-and-lists.md +596 -0
  195. package/templates/docs/06-localization.md +561 -0
  196. package/templates/docs/07-caching-strategies.md +410 -0
  197. package/templates/docs/08-common-components.md +756 -0
  198. package/templates/docs/09-whats-included.md +279 -0
  199. package/templates/docs/10-mcp-server-setup.md +153 -0
  200. package/templates/docs/11-linked-nested-content.md +611 -0
  201. package/templates/docs/README.md +164 -0
  202. package/templates/lib/cms/getAgilityContext.ts +28 -0
  203. package/templates/lib/cms/getAgilityPage.ts +51 -0
  204. package/templates/lib/cms/getAgilitySDK.ts +22 -0
  205. package/templates/lib/cms/getContentItem.ts +20 -0
  206. package/templates/lib/cms/getContentList.ts +19 -0
  207. package/templates/lib/cms/getRedirections.ts +85 -0
  208. package/templates/lib/cms/getSitemapFlat.ts +19 -0
  209. package/templates/lib/cms/getSitemapNested.ts +19 -0
  210. package/templates/lib/env.ts +99 -0
  211. package/templates/lib/i18n/config.ts +28 -0
  212. package/templates/proxy.ts +101 -0
  213. package/tsconfig.json +21 -0
@@ -0,0 +1,561 @@
1
+ # Localization (i18n)
2
+
3
+ This document explains how to implement multi-language support in your Agility CMS Next.js application.
4
+
5
+ ## Locale Configuration
6
+
7
+ Locales are configured in `src/lib/i18n/config.ts`:
8
+
9
+ ```typescript
10
+ export const defaultLocale = "en-us"
11
+ export const locales = ["en-us", "fr", "es"] as const
12
+
13
+ export type Locale = (typeof locales)[number]
14
+
15
+ export function isValidLocale(locale: string, locales: readonly string[]): boolean {
16
+ return locales.includes(locale)
17
+ }
18
+
19
+ export function getLocaleFromPathname(
20
+ pathname: string,
21
+ locales: readonly string[]
22
+ ): string | null {
23
+ const segments = pathname.split("/").filter(Boolean)
24
+ const firstSegment = segments[0]
25
+ return firstSegment && locales.includes(firstSegment) ? firstSegment : null
26
+ }
27
+
28
+ export function removeLocaleFromPathname(
29
+ pathname: string,
30
+ locale: string
31
+ ): string {
32
+ if (pathname.startsWith(`/${locale}`)) {
33
+ return pathname.slice(`/${locale}`.length) || "/"
34
+ }
35
+ return pathname
36
+ }
37
+ ```
38
+
39
+ ## URL Structure
40
+
41
+ ### Default Locale (Clean URLs)
42
+ The default locale doesn't appear in URLs:
43
+ ```
44
+ / → en-us homepage
45
+ /about → en-us about page
46
+ /blog/my-post → en-us blog post
47
+ ```
48
+
49
+ ### Other Locales (Prefixed)
50
+ Other locales have a prefix:
51
+ ```
52
+ /fr → French homepage
53
+ /fr/a-propos → French about page
54
+ /es/blog/mi-post → Spanish blog post
55
+ ```
56
+
57
+ ## Environment Configuration
58
+
59
+ Configure locales in `.env.local`:
60
+
61
+ ```env
62
+ AGILITY_LOCALES=en-us,fr,es
63
+ ```
64
+
65
+ ## Adding a New Locale
66
+
67
+ ### Step 1: Update Configuration
68
+
69
+ 1. Add to environment variable:
70
+ ```env
71
+ AGILITY_LOCALES=en-us,fr,es,de
72
+ ```
73
+
74
+ 2. Update config file:
75
+ ```typescript
76
+ // src/lib/i18n/config.ts
77
+ export const locales = ["en-us", "fr", "es", "de"] as const
78
+ ```
79
+
80
+ ### Step 2: Add Content in Agility CMS
81
+
82
+ 1. Go to **Settings > Locales**
83
+ 2. Add the new locale (e.g., "de" - German)
84
+ 3. Add content for all pages and modules in the new locale
85
+
86
+ ### Step 3: Rebuild
87
+
88
+ ```bash
89
+ npm run build
90
+ ```
91
+
92
+ All pages will be generated for the new locale.
93
+
94
+ ## Locale Detection and Routing
95
+
96
+ The proxy handles locale routing:
97
+
98
+ ```typescript
99
+ // proxy.ts
100
+
101
+ export async function proxy(request: NextRequest) {
102
+ const pathname = request.nextUrl.pathname
103
+ const hasLocalePrefix = locales.some(
104
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
105
+ )
106
+
107
+ // If no locale prefix, rewrite to default locale
108
+ if (!hasLocalePrefix) {
109
+ const localeBasedUrl = new URL(`/${defaultLocale}${pathname}`, request.url)
110
+ return NextResponse.rewrite(localeBasedUrl)
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### Lang Query Parameter
116
+
117
+ You can switch locales using `?lang=` parameter:
118
+
119
+ ```
120
+ /about?lang=fr → Redirects to /fr/about
121
+ /fr/about?lang=es → Redirects to /es/about
122
+ /about?lang=en-us → Redirects to /about (default locale)
123
+ ```
124
+
125
+ ## Fetching Localized Content
126
+
127
+ ### In Page Components
128
+
129
+ ```tsx
130
+ // app/[locale]/[...slug]/page.tsx
131
+
132
+ export default async function Page({ params }: PageProps) {
133
+ const { locale } = await params;
134
+
135
+ const agilityData = await getAgilityPage({ params });
136
+
137
+ // Data is automatically fetched for the current locale
138
+ return <div>{/* ... */}</div>;
139
+ }
140
+ ```
141
+
142
+ ### In Module Components
143
+
144
+ ```tsx
145
+ // components/agility-components/PostListing.tsx
146
+
147
+ import { getContentList } from "@/lib/cms/getContentList";
148
+
149
+ export default async function PostListing({ module, locale }: any) {
150
+ // Always pass locale to CMS functions
151
+ const posts = await getContentList({
152
+ referenceName: "posts",
153
+ locale, // Current locale from props
154
+ take: 10,
155
+ });
156
+
157
+ return <div>{/* ... */}</div>;
158
+ }
159
+ ```
160
+
161
+ ### Manual Locale Selection
162
+
163
+ ```tsx
164
+ import { getContentItem } from "@/lib/cms/getContentItem";
165
+
166
+ // Fetch content in specific locale
167
+ const frenchPost = await getContentItem({
168
+ contentID: 123,
169
+ locale: "fr",
170
+ });
171
+
172
+ const spanishPost = await getContentItem({
173
+ contentID: 123,
174
+ locale: "es",
175
+ });
176
+ ```
177
+
178
+ ## Locale Switcher Component
179
+
180
+ ### Simple Locale Switcher
181
+
182
+ ```tsx
183
+ // components/LocaleSwitcher.tsx
184
+
185
+ import { locales, defaultLocale } from "@/lib/i18n/config";
186
+
187
+ interface LocaleSwitcherProps {
188
+ currentLocale: string;
189
+ currentPath: string;
190
+ }
191
+
192
+ export default function LocaleSwitcher({
193
+ currentLocale,
194
+ currentPath,
195
+ }: LocaleSwitcherProps) {
196
+ return (
197
+ <div className="flex gap-2">
198
+ {locales.map((locale) => {
199
+ // Build the locale-specific URL
200
+ let href = currentPath;
201
+ if (locale === defaultLocale) {
202
+ // Remove locale prefix for default locale
203
+ href = currentPath.replace(/^\/[a-z]{2}(-[a-z]{2})?/, "") || "/";
204
+ } else {
205
+ // Add or replace locale prefix
206
+ href = currentPath.replace(/^\/[a-z]{2}(-[a-z]{2})?/, "");
207
+ href = `/${locale}${href}`;
208
+ }
209
+
210
+ return (
211
+ <a
212
+ key={locale}
213
+ href={href}
214
+ className={locale === currentLocale ? "font-bold" : ""}
215
+ >
216
+ {locale.toUpperCase()}
217
+ </a>
218
+ );
219
+ })}
220
+ </div>
221
+ );
222
+ }
223
+ ```
224
+
225
+ ### Using in Layout
226
+
227
+ ```tsx
228
+ // app/[locale]/layout.tsx
229
+
230
+ import LocaleSwitcher from "@/components/LocaleSwitcher";
231
+
232
+ export default async function LocaleLayout({
233
+ children,
234
+ params,
235
+ }: {
236
+ children: React.ReactNode;
237
+ params: Promise<{ locale: string }>;
238
+ }) {
239
+ const { locale } = await params;
240
+
241
+ return (
242
+ <html lang={locale}>
243
+ <body>
244
+ <header>
245
+ <LocaleSwitcher currentLocale={locale} currentPath="/" />
246
+ </header>
247
+ {children}
248
+ </body>
249
+ </html>
250
+ );
251
+ }
252
+ ```
253
+
254
+ ## Translation Helpers
255
+
256
+ ### Creating Translation Files
257
+
258
+ ```typescript
259
+ // lib/i18n/translations.ts
260
+
261
+ export const translations = {
262
+ "en-us": {
263
+ nav: {
264
+ home: "Home",
265
+ about: "About",
266
+ contact: "Contact",
267
+ },
268
+ common: {
269
+ readMore: "Read More",
270
+ loadMore: "Load More",
271
+ backToHome: "Back to Home",
272
+ },
273
+ },
274
+ fr: {
275
+ nav: {
276
+ home: "Accueil",
277
+ about: "À propos",
278
+ contact: "Contact",
279
+ },
280
+ common: {
281
+ readMore: "Lire la suite",
282
+ loadMore: "Charger plus",
283
+ backToHome: "Retour à l'accueil",
284
+ },
285
+ },
286
+ es: {
287
+ nav: {
288
+ home: "Inicio",
289
+ about: "Acerca de",
290
+ contact: "Contacto",
291
+ },
292
+ common: {
293
+ readMore: "Leer más",
294
+ loadMore: "Cargar más",
295
+ backToHome: "Volver al inicio",
296
+ },
297
+ },
298
+ } as const;
299
+
300
+ export function t(locale: string, key: string) {
301
+ const keys = key.split(".");
302
+ let value: any = translations[locale as keyof typeof translations];
303
+
304
+ for (const k of keys) {
305
+ value = value?.[k];
306
+ }
307
+
308
+ return value || key;
309
+ }
310
+ ```
311
+
312
+ ### Using Translations
313
+
314
+ ```tsx
315
+ // components/Header.tsx
316
+
317
+ import { t } from "@/lib/i18n/translations";
318
+
319
+ export default function Header({ locale }: { locale: string }) {
320
+ return (
321
+ <nav>
322
+ <a href="/">{t(locale, "nav.home")}</a>
323
+ <a href="/about">{t(locale, "nav.about")}</a>
324
+ <a href="/contact">{t(locale, "nav.contact")}</a>
325
+ </nav>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ## Date and Number Formatting
331
+
332
+ ### Date Formatting
333
+
334
+ ```tsx
335
+ // lib/i18n/formatters.ts
336
+
337
+ export function formatDate(date: string, locale: string): string {
338
+ return new Date(date).toLocaleDateString(locale, {
339
+ year: "numeric",
340
+ month: "long",
341
+ day: "numeric",
342
+ });
343
+ }
344
+ ```
345
+
346
+ Usage:
347
+ ```tsx
348
+ import { formatDate } from "@/lib/i18n/formatters";
349
+
350
+ export default function BlogPost({ post, locale }: any) {
351
+ return (
352
+ <article>
353
+ <h1>{post.fields.title}</h1>
354
+ <time>{formatDate(post.fields.date, locale)}</time>
355
+ <div dangerouslySetInnerHTML={{ __html: post.fields.content }} />
356
+ </article>
357
+ );
358
+ }
359
+ ```
360
+
361
+ ### Number Formatting
362
+
363
+ ```tsx
364
+ export function formatNumber(num: number, locale: string): string {
365
+ return new Intl.NumberFormat(locale).format(num);
366
+ }
367
+
368
+ export function formatCurrency(
369
+ amount: number,
370
+ locale: string,
371
+ currency: string = "USD"
372
+ ): string {
373
+ return new Intl.NumberFormat(locale, {
374
+ style: "currency",
375
+ currency,
376
+ }).format(amount);
377
+ }
378
+ ```
379
+
380
+ ## Locale-Specific Sitemap
381
+
382
+ Each locale has its own sitemap in Agility CMS:
383
+
384
+ ```typescript
385
+ // Generate pages for each locale
386
+ export async function generateStaticParams() {
387
+ const allPaths: { locale: string; slug: string[] }[] = [];
388
+
389
+ for (const locale of locales) {
390
+ const sitemap = await agilityClient.getSitemapFlat({
391
+ channelName: "website",
392
+ languageCode: locale,
393
+ });
394
+
395
+ const localePaths = Object.values(sitemap).map((node: any) => ({
396
+ locale,
397
+ slug: node.path.split("/").slice(1),
398
+ }));
399
+
400
+ allPaths.push(...localePaths);
401
+ }
402
+
403
+ return allPaths;
404
+ }
405
+ ```
406
+
407
+ ## SEO for Multi-Locale
408
+
409
+ ### Alternate Language Links
410
+
411
+ ```tsx
412
+ // app/[locale]/[...slug]/page.tsx
413
+
414
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
415
+ const { locale, slug } = await params;
416
+ const agilityData = await getAgilityPage({ params });
417
+
418
+ const alternates: Record<string, string> = {};
419
+
420
+ for (const loc of locales) {
421
+ if (loc === defaultLocale) {
422
+ alternates[loc] = `https://yourdomain.com${agilityData.sitemapNode.path}`;
423
+ } else {
424
+ alternates[loc] = `https://yourdomain.com/${loc}${agilityData.sitemapNode.path}`;
425
+ }
426
+ }
427
+
428
+ return {
429
+ title: agilityData.page?.seo?.metaTitle,
430
+ description: agilityData.page?.seo?.metaDescription,
431
+ alternates: {
432
+ languages: alternates,
433
+ },
434
+ };
435
+ }
436
+ ```
437
+
438
+ ### Sitemap.xml with Locales
439
+
440
+ ```tsx
441
+ // app/sitemap.ts
442
+
443
+ import { locales, defaultLocale } from "@/lib/i18n/config";
444
+ import { getSitemapFlat } from "@/lib/cms/getSitemapFlat";
445
+
446
+ export default async function sitemap() {
447
+ const urls = [];
448
+
449
+ for (const locale of locales) {
450
+ const sitemap = await getSitemapFlat({ locale });
451
+
452
+ for (const path in sitemap) {
453
+ const url =
454
+ locale === defaultLocale
455
+ ? `https://yourdomain.com${path}`
456
+ : `https://yourdomain.com/${locale}${path}`;
457
+
458
+ urls.push({
459
+ url,
460
+ lastModified: new Date(),
461
+ alternates: {
462
+ languages: locales.reduce((acc, loc) => {
463
+ acc[loc] =
464
+ loc === defaultLocale
465
+ ? `https://yourdomain.com${path}`
466
+ : `https://yourdomain.com/${loc}${path}`;
467
+ return acc;
468
+ }, {} as Record<string, string>),
469
+ },
470
+ });
471
+ }
472
+ }
473
+
474
+ return urls;
475
+ }
476
+ ```
477
+
478
+ ## Right-to-Left (RTL) Support
479
+
480
+ For RTL languages like Arabic:
481
+
482
+ ```tsx
483
+ // app/[locale]/layout.tsx
484
+
485
+ const rtlLocales = ["ar", "he"];
486
+
487
+ export default function LocaleLayout({ children, params }: any) {
488
+ const { locale } = params;
489
+ const dir = rtlLocales.includes(locale) ? "rtl" : "ltr";
490
+
491
+ return (
492
+ <html lang={locale} dir={dir}>
493
+ <body>{children}</body>
494
+ </html>
495
+ );
496
+ }
497
+ ```
498
+
499
+ ## Best Practices
500
+
501
+ 1. **Always Pass Locale**: Pass locale to all CMS functions
502
+ 2. **Use Translation Keys**: Don't hardcode strings in components
503
+ 3. **Format Dates/Numbers**: Use locale-specific formatting
504
+ 4. **SEO Optimization**: Add alternate language links
505
+ 5. **Test All Locales**: Ensure content exists for all locales
506
+ 6. **Fallback Content**: Handle missing translations gracefully
507
+ 7. **URL Structure**: Keep URLs SEO-friendly in each locale
508
+
509
+ ## Common Patterns
510
+
511
+ ### Pattern 1: Locale-Aware Navigation
512
+
513
+ ```tsx
514
+ import { getSitemapNested } from "@/lib/cms/getSitemapNested";
515
+
516
+ export default async function Navigation({ locale }: { locale: string }) {
517
+ const sitemap = await getSitemapNested({ locale });
518
+
519
+ return (
520
+ <nav>
521
+ {sitemap[0].children.map((item: any) => (
522
+ <a key={item.pageID} href={item.path}>
523
+ {item.menuText || item.title}
524
+ </a>
525
+ ))}
526
+ </nav>
527
+ );
528
+ }
529
+ ```
530
+
531
+ ### Pattern 2: Locale-Specific Footer
532
+
533
+ ```tsx
534
+ import { getContentItem } from "@/lib/cms/getContentItem";
535
+
536
+ export default async function Footer({ locale }: { locale: string }) {
537
+ const footer = await getContentItem({
538
+ referenceName: "footerSettings",
539
+ locale,
540
+ });
541
+
542
+ return (
543
+ <footer>
544
+ <p>{footer.fields.copyrightText}</p>
545
+ <nav>
546
+ {footer.fields.links.map((link: any) => (
547
+ <a key={link.href} href={link.href}>
548
+ {link.text}
549
+ </a>
550
+ ))}
551
+ </nav>
552
+ </footer>
553
+ );
554
+ }
555
+ ```
556
+
557
+ ## Next Steps
558
+
559
+ - Read [02-page-routing.md](./02-page-routing.md) for locale routing details
560
+ - Read [04-data-fetching.md](./04-data-fetching.md) for fetching localized content
561
+ - Read [07-caching-strategies.md](./07-caching-strategies.md) for locale-specific caching