@rimelight/ui 0.0.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.
Files changed (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/i18n-data.json +186 -0
  4. package/package.json +49 -0
  5. package/src/components/FormattedDate.astro +22 -0
  6. package/src/components/HeaderLink.astro +24 -0
  7. package/src/components/HttpObservatory.astro +103 -0
  8. package/src/components/LighthouseScores.astro +204 -0
  9. package/src/components/MissingTranslationBanner.astro +39 -0
  10. package/src/components/TableOfContents.astro +106 -0
  11. package/src/components/ThemeToggle.astro +71 -0
  12. package/src/components/accordion/Accordion.astro +257 -0
  13. package/src/components/accordion/AccordionContent.astro +34 -0
  14. package/src/components/accordion/AccordionItem.astro +29 -0
  15. package/src/components/accordion/AccordionTrigger.astro +33 -0
  16. package/src/components/alert/Alert.astro +34 -0
  17. package/src/components/alert/AlertDescription.astro +15 -0
  18. package/src/components/alert/AlertTitle.astro +17 -0
  19. package/src/components/alert-dialog/AlertDialog.astro +275 -0
  20. package/src/components/alert-dialog/AlertDialogAction.astro +46 -0
  21. package/src/components/alert-dialog/AlertDialogCancel.astro +46 -0
  22. package/src/components/alert-dialog/AlertDialogContent.astro +52 -0
  23. package/src/components/alert-dialog/AlertDialogDescription.astro +18 -0
  24. package/src/components/alert-dialog/AlertDialogFooter.astro +16 -0
  25. package/src/components/alert-dialog/AlertDialogHeader.astro +14 -0
  26. package/src/components/alert-dialog/AlertDialogTitle.astro +20 -0
  27. package/src/components/alert-dialog/AlertDialogTrigger.astro +47 -0
  28. package/src/components/aspect-ratio/AspectRatio.astro +32 -0
  29. package/src/components/avatar/Avatar.astro +29 -0
  30. package/src/components/avatar/AvatarFallback.astro +18 -0
  31. package/src/components/avatar/AvatarImage.astro +66 -0
  32. package/src/components/badge/Badge.astro +57 -0
  33. package/src/components/breadcrumb/Breadcrumb.astro +12 -0
  34. package/src/components/breadcrumb/BreadcrumbEllipsis.astro +29 -0
  35. package/src/components/breadcrumb/BreadcrumbItem.astro +15 -0
  36. package/src/components/breadcrumb/BreadcrumbLink.astro +24 -0
  37. package/src/components/breadcrumb/BreadcrumbList.astro +17 -0
  38. package/src/components/breadcrumb/BreadcrumbPage.astro +22 -0
  39. package/src/components/breadcrumb/BreadcrumbSeparator.astro +24 -0
  40. package/src/components/button/Button.astro +58 -0
  41. package/src/components/button-group/ButtonGroup.astro +64 -0
  42. package/src/components/button-group/ButtonGroupSeparator.astro +20 -0
  43. package/src/components/button-group/ButtonGroupText.astro +19 -0
  44. package/src/components/card/Card.astro +36 -0
  45. package/src/components/card/CardAction.astro +16 -0
  46. package/src/components/card/CardContent.astro +16 -0
  47. package/src/components/card/CardDescription.astro +16 -0
  48. package/src/components/card/CardFooter.astro +16 -0
  49. package/src/components/card/CardHeader.astro +19 -0
  50. package/src/components/card/CardTitle.astro +16 -0
  51. package/src/components/carousel/Carousel.astro +55 -0
  52. package/src/components/carousel/CarouselContent.astro +26 -0
  53. package/src/components/carousel/CarouselItem.astro +26 -0
  54. package/src/components/carousel/CarouselNext.astro +39 -0
  55. package/src/components/carousel/CarouselPrevious.astro +41 -0
  56. package/src/components/carousel/carousel-script.ts +188 -0
  57. package/src/components/carousel/index.ts +1 -0
  58. package/src/components/checkbox/Checkbox.astro +133 -0
  59. package/src/components/collapsible/Collapsible.astro +161 -0
  60. package/src/components/collapsible/CollapsibleContent.astro +22 -0
  61. package/src/components/collapsible/CollapsibleTrigger.astro +44 -0
  62. package/src/components/dialog/Dialog.astro +389 -0
  63. package/src/components/dialog/DialogClose.astro +35 -0
  64. package/src/components/dialog/DialogContent.astro +78 -0
  65. package/src/components/dialog/DialogDescription.astro +14 -0
  66. package/src/components/dialog/DialogFooter.astro +14 -0
  67. package/src/components/dialog/DialogHeader.astro +14 -0
  68. package/src/components/dialog/DialogTitle.astro +18 -0
  69. package/src/components/dialog/DialogTrigger.astro +47 -0
  70. package/src/components/dropdown/Dropdown.astro +604 -0
  71. package/src/components/dropdown/DropdownContent.astro +118 -0
  72. package/src/components/dropdown/DropdownItem.astro +48 -0
  73. package/src/components/dropdown/DropdownLabel.astro +29 -0
  74. package/src/components/dropdown/DropdownSeparator.astro +21 -0
  75. package/src/components/dropdown/DropdownShortcut.astro +17 -0
  76. package/src/components/dropdown/DropdownSub.astro +15 -0
  77. package/src/components/dropdown/DropdownSubContent.astro +39 -0
  78. package/src/components/dropdown/DropdownSubTrigger.astro +34 -0
  79. package/src/components/dropdown/DropdownTrigger.astro +52 -0
  80. package/src/components/dropzone/Dropzone.astro +236 -0
  81. package/src/components/dropzone/DropzoneFilesList.astro +26 -0
  82. package/src/components/dropzone/DropzoneLoadingIndicator.astro +10 -0
  83. package/src/components/dropzone/DropzoneUploadIndicator.astro +10 -0
  84. package/src/components/embeds/YoutubeEmbed.astro +94 -0
  85. package/src/components/hero/Hero.astro +166 -0
  86. package/src/components/icons/Flag.astro +62 -0
  87. package/src/components/image/ExtendedImage.astro +46 -0
  88. package/src/components/image/Image.astro +25 -0
  89. package/src/components/input/Input.astro +27 -0
  90. package/src/components/input-group/InputGroup.astro +28 -0
  91. package/src/components/input-group/InputGroupAddon.astro +76 -0
  92. package/src/components/input-group/InputGroupButton.astro +36 -0
  93. package/src/components/input-group/InputGroupInput.astro +18 -0
  94. package/src/components/input-group/InputGroupText.astro +17 -0
  95. package/src/components/input-group/InputGroupTextarea.astro +22 -0
  96. package/src/components/input-otp/InputOtp.astro +352 -0
  97. package/src/components/input-otp/InputOtpGroup.astro +16 -0
  98. package/src/components/input-otp/InputOtpSeparator.astro +25 -0
  99. package/src/components/input-otp/InputOtpSlot.astro +50 -0
  100. package/src/components/input-otp/InputOtpTypes.ts +6 -0
  101. package/src/components/item/Item.astro +52 -0
  102. package/src/components/item/ItemActions.astro +16 -0
  103. package/src/components/item/ItemContent.astro +16 -0
  104. package/src/components/item/ItemDescription.astro +19 -0
  105. package/src/components/item/ItemFooter.astro +16 -0
  106. package/src/components/item/ItemGroup.astro +16 -0
  107. package/src/components/item/ItemHeader.astro +16 -0
  108. package/src/components/item/ItemMedia.astro +40 -0
  109. package/src/components/item/ItemSeparator.astro +23 -0
  110. package/src/components/item/ItemTitle.astro +16 -0
  111. package/src/components/kbd/Kbd.astro +21 -0
  112. package/src/components/kbd/KbdGroup.astro +16 -0
  113. package/src/components/label/Label.astro +24 -0
  114. package/src/components/marquee/Marquee.astro +73 -0
  115. package/src/components/native-select/NativeSelect.astro +68 -0
  116. package/src/components/native-select/NativeSelectOptGroup.astro +15 -0
  117. package/src/components/native-select/NativeSelectOption.astro +15 -0
  118. package/src/components/pagination/Pagination.astro +20 -0
  119. package/src/components/pagination/PaginationContent.astro +16 -0
  120. package/src/components/pagination/PaginationEllipsis.astro +37 -0
  121. package/src/components/pagination/PaginationItem.astro +16 -0
  122. package/src/components/pagination/PaginationLink.astro +27 -0
  123. package/src/components/pagination/PaginationNext.astro +33 -0
  124. package/src/components/pagination/PaginationPrevious.astro +33 -0
  125. package/src/components/popover/Popover.astro +717 -0
  126. package/src/components/popover/PopoverContent.astro +102 -0
  127. package/src/components/popover/PopoverDescription.astro +14 -0
  128. package/src/components/popover/PopoverHeader.astro +14 -0
  129. package/src/components/popover/PopoverTitle.astro +14 -0
  130. package/src/components/popover/PopoverTrigger.astro +51 -0
  131. package/src/components/progress/Progress.astro +154 -0
  132. package/src/components/prose/Prose.astro +619 -0
  133. package/src/components/radio-group/RadioGroup.astro +162 -0
  134. package/src/components/radio-group/RadioGroupItem.astro +133 -0
  135. package/src/components/radio-group/RadioGroupTypes.ts +6 -0
  136. package/src/components/section/Section.astro +167 -0
  137. package/src/components/select/Select.astro +752 -0
  138. package/src/components/select/SelectContent.astro +94 -0
  139. package/src/components/select/SelectGroup.astro +9 -0
  140. package/src/components/select/SelectItem.astro +51 -0
  141. package/src/components/select/SelectLabel.astro +14 -0
  142. package/src/components/select/SelectSearch.astro +49 -0
  143. package/src/components/select/SelectSeparator.astro +12 -0
  144. package/src/components/select/SelectTrigger.astro +56 -0
  145. package/src/components/select/SelectTypes.ts +13 -0
  146. package/src/components/select/SelectValue.astro +19 -0
  147. package/src/components/separator/Separator.astro +36 -0
  148. package/src/components/sheet/Sheet.astro +15 -0
  149. package/src/components/sheet/SheetClose.astro +15 -0
  150. package/src/components/sheet/SheetContent.astro +89 -0
  151. package/src/components/sheet/SheetDescription.astro +16 -0
  152. package/src/components/sheet/SheetFooter.astro +16 -0
  153. package/src/components/sheet/SheetHeader.astro +16 -0
  154. package/src/components/sheet/SheetTitle.astro +16 -0
  155. package/src/components/sheet/SheetTrigger.astro +15 -0
  156. package/src/components/sidebar/Sidebar.astro +231 -0
  157. package/src/components/sidebar/SidebarContent.astro +25 -0
  158. package/src/components/sidebar/SidebarFooter.astro +22 -0
  159. package/src/components/sidebar/SidebarGroup.astro +22 -0
  160. package/src/components/sidebar/SidebarGroupContent.astro +22 -0
  161. package/src/components/sidebar/SidebarGroupLabel.astro +53 -0
  162. package/src/components/sidebar/SidebarHeader.astro +22 -0
  163. package/src/components/sidebar/SidebarInput.astro +24 -0
  164. package/src/components/sidebar/SidebarInset.astro +22 -0
  165. package/src/components/sidebar/SidebarMenu.astro +22 -0
  166. package/src/components/sidebar/SidebarMenuAction.astro +63 -0
  167. package/src/components/sidebar/SidebarMenuBadge.astro +31 -0
  168. package/src/components/sidebar/SidebarMenuButton.astro +124 -0
  169. package/src/components/sidebar/SidebarMenuItem.astro +22 -0
  170. package/src/components/sidebar/SidebarMenuSkeleton.astro +43 -0
  171. package/src/components/sidebar/SidebarMenuSub.astro +25 -0
  172. package/src/components/sidebar/SidebarMenuSubButton.astro +52 -0
  173. package/src/components/sidebar/SidebarMenuSubItem.astro +17 -0
  174. package/src/components/sidebar/SidebarProvider.astro +215 -0
  175. package/src/components/sidebar/SidebarRail.astro +72 -0
  176. package/src/components/sidebar/SidebarSeparator.astro +25 -0
  177. package/src/components/sidebar/SidebarTrigger.astro +69 -0
  178. package/src/components/skeleton/Skeleton.astro +14 -0
  179. package/src/components/slider/Slider.astro +425 -0
  180. package/src/components/spinner/Spinner.astro +23 -0
  181. package/src/components/switch/Switch.astro +192 -0
  182. package/src/components/switch/SwitchTypes.ts +6 -0
  183. package/src/components/table/Table.astro +20 -0
  184. package/src/components/table/TableBody.astro +18 -0
  185. package/src/components/table/TableCaption.astro +18 -0
  186. package/src/components/table/TableCell.astro +18 -0
  187. package/src/components/table/TableFoot.astro +18 -0
  188. package/src/components/table/TableHead.astro +18 -0
  189. package/src/components/table/TableHeader.astro +18 -0
  190. package/src/components/table/TableRow.astro +17 -0
  191. package/src/components/tabs/Tabs.astro +270 -0
  192. package/src/components/tabs/TabsContent.astro +28 -0
  193. package/src/components/tabs/TabsList.astro +23 -0
  194. package/src/components/tabs/TabsTrigger.astro +34 -0
  195. package/src/components/textarea/Textarea.astro +31 -0
  196. package/src/components/toast/ToastDescription.astro +22 -0
  197. package/src/components/toast/ToastItem.astro +56 -0
  198. package/src/components/toast/ToastTemplate.astro +27 -0
  199. package/src/components/toast/ToastTitle.astro +59 -0
  200. package/src/components/toast/Toaster.astro +981 -0
  201. package/src/components/toast/index.ts +1 -0
  202. package/src/components/toast/toast-manager.ts +234 -0
  203. package/src/components/toggle/Toggle.astro +174 -0
  204. package/src/components/toggle/ToggleTypes.ts +14 -0
  205. package/src/components/tooltip/Tooltip.astro +422 -0
  206. package/src/components/tooltip/TooltipContent.astro +83 -0
  207. package/src/components/tooltip/TooltipTrigger.astro +13 -0
  208. package/src/components/video/Video.astro +121 -0
  209. package/src/config/cookies.ts +5 -0
  210. package/src/config/index.ts +3 -0
  211. package/src/config/links.ts +4 -0
  212. package/src/config/site.config.ts +45 -0
  213. package/src/domain/cms/index.ts +1 -0
  214. package/src/domain/cms/types.ts +20 -0
  215. package/src/domain/i18n/constants.ts +21 -0
  216. package/src/domain/i18n/cookie-storage.ts +13 -0
  217. package/src/domain/i18n/country-to-locale-map.ts +67 -0
  218. package/src/domain/i18n/fallbacks.generated.ts +230 -0
  219. package/src/domain/i18n/fetcher.ts +268 -0
  220. package/src/domain/i18n/flags.ts +16 -0
  221. package/src/domain/i18n/format.ts +90 -0
  222. package/src/domain/i18n/index.ts +10 -0
  223. package/src/domain/i18n/middleware/i18n.ts +160 -0
  224. package/src/domain/i18n/resolve-locale.ts +55 -0
  225. package/src/domain/i18n/runtime-constants.ts +14 -0
  226. package/src/domain/i18n/schema.ts +29 -0
  227. package/src/domain/index.ts +4 -0
  228. package/src/domain/seo/constants.ts +20 -0
  229. package/src/domain/seo/index.ts +3 -0
  230. package/src/domain/seo/services/index.ts +3 -0
  231. package/src/domain/seo/services/llms.ts +53 -0
  232. package/src/domain/seo/services/robots.ts +12 -0
  233. package/src/domain/seo/services/sitemap.ts +56 -0
  234. package/src/domain/seo/utils.ts +37 -0
  235. package/src/domain/theme/constants.ts +6 -0
  236. package/src/domain/theme/index.ts +2 -0
  237. package/src/domain/theme/schema.ts +10 -0
  238. package/src/env.d.ts +49 -0
  239. package/src/i18n.generated.d.ts +202 -0
  240. package/src/lib/component-preview.ts +73 -0
  241. package/src/lib/index.ts +6 -0
  242. package/src/lib/remark/remark-modified-time.ts +13 -0
  243. package/src/lib/showcase-preview-elements.ts +408 -0
  244. package/src/lib/showcase-renderer.ts +218 -0
  245. package/src/lib/showcase-utils.ts +189 -0
  246. package/src/lib/turnstile.ts +35 -0
  247. package/src/lib/utils/starwind/positioning.ts +318 -0
  248. package/src/locales/de/blog.json +9 -0
  249. package/src/locales/de/common.json +19 -0
  250. package/src/locales/de/errors.json +6 -0
  251. package/src/locales/de/home.json +20 -0
  252. package/src/locales/de/messages.json +8 -0
  253. package/src/locales/de/nav.json +13 -0
  254. package/src/locales/de/projects.json +9 -0
  255. package/src/locales/de/services.json +43 -0
  256. package/src/locales/en/blog.json +9 -0
  257. package/src/locales/en/chat.json +29 -0
  258. package/src/locales/en/common.json +19 -0
  259. package/src/locales/en/cookie_notice.json +11 -0
  260. package/src/locales/en/editor.json +24 -0
  261. package/src/locales/en/errors.json +6 -0
  262. package/src/locales/en/home.json +20 -0
  263. package/src/locales/en/messages.json +8 -0
  264. package/src/locales/en/nav.json +13 -0
  265. package/src/locales/en/projects.json +9 -0
  266. package/src/locales/en/services.json +43 -0
  267. package/src/locales/es/blog.json +9 -0
  268. package/src/locales/es/common.json +19 -0
  269. package/src/locales/es/errors.json +6 -0
  270. package/src/locales/es/home.json +20 -0
  271. package/src/locales/es/messages.json +8 -0
  272. package/src/locales/es/nav.json +13 -0
  273. package/src/locales/es/projects.json +9 -0
  274. package/src/locales/es/services.json +43 -0
  275. package/src/locales/ja/blog.json +9 -0
  276. package/src/locales/ja/common.json +19 -0
  277. package/src/locales/ja/errors.json +6 -0
  278. package/src/locales/ja/home.json +20 -0
  279. package/src/locales/ja/messages.json +8 -0
  280. package/src/locales/ja/nav.json +13 -0
  281. package/src/locales/ja/projects.json +9 -0
  282. package/src/locales/ja/services.json +43 -0
  283. package/src/locales/pt/blog.json +9 -0
  284. package/src/locales/pt/chat.json +29 -0
  285. package/src/locales/pt/common.json +19 -0
  286. package/src/locales/pt/cookie_notice.json +11 -0
  287. package/src/locales/pt/editor.json +24 -0
  288. package/src/locales/pt/errors.json +6 -0
  289. package/src/locales/pt/home.json +20 -0
  290. package/src/locales/pt/messages.json +8 -0
  291. package/src/locales/pt/nav.json +13 -0
  292. package/src/locales/pt/projects.json +9 -0
  293. package/src/locales/pt/services.json +43 -0
  294. package/src/utils/index.ts +2 -0
  295. package/src/utils/server/cookies.ts +98 -0
  296. package/src/utils/server/index.ts +1 -0
  297. package/src/utils/shared/deep-merge.ts +36 -0
  298. package/src/utils/shared/embed.ts +105 -0
  299. package/src/utils/shared/index.ts +3 -0
  300. package/src/utils/shared/parse-boolean.ts +14 -0
  301. package/worker-configuration.d.ts +14791 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal shape of an Astro Content Collection entry. Explicitly allowing undefined for all common
3
+ * SEO fields to resolve monorepo type clashes.
4
+ */
5
+ export interface BaseCMSContentEntry {
6
+ id: string
7
+ data: {
8
+ title?: string | undefined | null
9
+ description?: string | undefined | null
10
+ publishedDate?: Date | undefined | null
11
+ [key: string]: unknown
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Type guard to safely identify a content entry that satisfies the base library requirements.
17
+ */
18
+ export function isBaseCMSContentEntry(entry: unknown): entry is BaseCMSContentEntry {
19
+ return !!entry && typeof entry === "object" && entry !== null && "id" in entry && "data" in entry
20
+ }
@@ -0,0 +1,21 @@
1
+ // Locales Configuration
2
+ export const SUPPORTED_LOCALES = ["en", "pt", "ja", "de", "es"] as const
3
+ export type SupportedLocales = (typeof SUPPORTED_LOCALES)[number]
4
+
5
+ // Default locale
6
+ export const DEFAULT_LOCALE: SupportedLocales = "en"
7
+
8
+ // Missing translation banner flag
9
+ export const ENABLE_MISSING_TRANSLATION_BANNER = true
10
+
11
+ // UI Labels
12
+ export const LANGUAGES: Record<SupportedLocales, string> = {
13
+ en: "English",
14
+ pt: "Português",
15
+ ja: "日本語",
16
+ de: "Deutsch",
17
+ es: "Español"
18
+ }
19
+
20
+ // Cookie name
21
+ export const LANG_COOKIE_NAME = "lang"
@@ -0,0 +1,13 @@
1
+ import { type AstroCookies } from "astro"
2
+
3
+ import { type Locale, LocaleSchema } from "./schema"
4
+ import { setCookie, getCookieWithSchema } from "../../utils/server/cookies"
5
+ import { LANG_COOKIE_NAME } from "./constants"
6
+
7
+ export function setCookieLang(cookies: AstroCookies, locale: Locale) {
8
+ setCookie(cookies, LANG_COOKIE_NAME, locale)
9
+ }
10
+
11
+ export function getCookieLang(cookies: AstroCookies): Locale | null {
12
+ return getCookieWithSchema(cookies, LANG_COOKIE_NAME, LocaleSchema)
13
+ }
@@ -0,0 +1,67 @@
1
+ import type { Locale } from "./schema.ts"
2
+
3
+ // Key: ISO 3166-1 Alpha-2 Country Code (from request.cf.country)
4
+ // Value: The optimal language to serve
5
+ const GEO_MAP: Record<string, Locale> = {
6
+ // --- ANGLOSPHERE (Explicitly set to EN) ---
7
+ US: "en", // United States
8
+ GB: "en", // United Kingdom
9
+ AU: "en", // Australia
10
+ NZ: "en", // New Zealand
11
+ IE: "en", // Ireland
12
+ CA: "en", // Canada (Majority EN, fallbacks handle FR better via Accept-Language headers)
13
+
14
+ // --- DACH (German) ---
15
+ DE: "de",
16
+ AT: "de",
17
+ CH: "de",
18
+ LI: "de",
19
+
20
+ // --- LATAM + SPAIN ---
21
+ ES: "es",
22
+ MX: "es",
23
+ AR: "es",
24
+ CO: "es",
25
+ CL: "es",
26
+ PE: "es",
27
+ VE: "es",
28
+ GT: "es",
29
+ EC: "es",
30
+
31
+ // Asia
32
+ JP: "ja",
33
+
34
+ // --- LUSOPHONE (Portuguese) ---
35
+ PT: "pt",
36
+ BR: "pt",
37
+ AO: "pt",
38
+ MZ: "pt"
39
+
40
+ // --- FRANCOPHONE ---
41
+ // FR: 'fr',
42
+ // BE: 'fr',
43
+ // MC: 'fr',
44
+ // SN: 'fr',
45
+
46
+ // --- SLAVIC / POST-SOVIET ---
47
+ // UA: 'uk',
48
+ // RU: 'ru',
49
+ // KZ: 'ru',
50
+ // BY: 'ru',
51
+
52
+ // Add other mappings as needed...
53
+ }
54
+
55
+ /**
56
+ * Maps a Cloudflare Country Code to a supported App Locale. Returns undefined if no direct mapping
57
+ * exists (falling back to default strategy).
58
+ */
59
+ export function mapCountryToLocale(country: unknown): string | undefined {
60
+ // Safety: In dev mode (localhost), 'country' might be null or undefined.
61
+ if (typeof country !== "string") return undefined
62
+
63
+ // Cloudflare always sends uppercase (e.g., 'US', 'DE'), but safety first.
64
+ const code = country.toUpperCase()
65
+
66
+ return GEO_MAP[code]
67
+ }
@@ -0,0 +1,230 @@
1
+ // Auto-generated by scripts/bundle-translations.ts
2
+ // Do not edit manually.
3
+
4
+ export const FALLBACK_LOCALE = "en" as const
5
+
6
+ export const FALLBACK_BLOG = {
7
+ meta_title: "Blog | Daniel Marchi",
8
+ hero_title: "Blog",
9
+ hero_description: "My insights throughout my journey as a developer.",
10
+ latest_article: "Latest Article",
11
+ latest: "Latest",
12
+ read_article: "Read Article",
13
+ all_posts: "All Posts"
14
+ } as const
15
+
16
+ export const FALLBACK_CHAT = {
17
+ title: "Join a Chat Room",
18
+ description: "Enter a room name to start chatting",
19
+ heading: "Real-time Chat",
20
+ intro:
21
+ "Enter a room name below to join a conversation. Each room is a unique Durable Object instance!",
22
+ label: "Room Name",
23
+ placeholder: "e.g. general, help, coffee-break",
24
+ button: "Join Room",
25
+ room: {
26
+ title: "Chat Room",
27
+ connecting: "Connecting…",
28
+ connected: "Connected",
29
+ disconnected: "Disconnected",
30
+ placeholder: "Type a message…",
31
+ send: "Send",
32
+ system: {
33
+ joining: "Joining room…",
34
+ error: "Connection error",
35
+ joined: "joined the room",
36
+ left: "left the room",
37
+ reconnecting: "Reconnecting in {delay} seconds... (attempt {attempt}/{max})",
38
+ fail_reconnect: "Failed to reconnect. Please refresh the page.",
39
+ no_connect: "Cannot send message: not connected",
40
+ prompt_user: "Enter your username:",
41
+ you: "(You)",
42
+ online_users: "Online Users:"
43
+ }
44
+ }
45
+ } as const
46
+
47
+ export const FALLBACK_COMMON = {
48
+ seo: {
49
+ title: "Zero-JS i18n Astro Starter for Cloudflare Workers",
50
+ description:
51
+ "Ship multilingual apps faster without hydration bloat. Full integration with Cloudflare Workers & KV, automated TypeScript generation, and edge caching out of the box."
52
+ },
53
+ nav: {
54
+ home: "Astro components & islands",
55
+ blog: "Markdown i18n examples",
56
+ mobileButton: "Menu"
57
+ },
58
+ footer: {
59
+ license: "Under the MIT license."
60
+ },
61
+ notFound: {
62
+ title: "404 — Page not found",
63
+ message: "The page you are looking for does not exist or has been moved.",
64
+ backToHome: "Go back to homepage"
65
+ }
66
+ } as const
67
+
68
+ export const FALLBACK_COOKIE_NOTICE = {
69
+ title: "Cookie Policy",
70
+ description:
71
+ "This website does not use cookies, this is a sample of how it could work in your website as per your",
72
+ privacy_policy: "Privacy Policy",
73
+ reject_all: "Reject All",
74
+ accept_all: "Accept All",
75
+ accepted_toast: "Cookies Accepted",
76
+ accepted_description: "You have accepted all cookies.",
77
+ rejected_toast: "Cookies Rejected",
78
+ rejected_description: "You have rejected all cookies."
79
+ } as const
80
+
81
+ export const FALLBACK_EDITOR = {
82
+ title: "Editor",
83
+ description: "Collaborative real-time document editor",
84
+ collaborative_editor: "Collaborative Editor",
85
+ status: {
86
+ connecting: "Connecting…",
87
+ connected: "Connected",
88
+ disconnected: "Disconnected"
89
+ },
90
+ history: "History",
91
+ version_history: "Version History",
92
+ no_versions: "No versions yet.",
93
+ placeholder: "Start typing… your changes are synced in real-time.",
94
+ word_count: {
95
+ word: "word",
96
+ words: "words",
97
+ character: "character",
98
+ characters: "characters"
99
+ },
100
+ save: {
101
+ saved: "Saved"
102
+ },
103
+ restore_confirm: "Restore this version? Current content will be replaced."
104
+ } as const
105
+
106
+ export const FALLBACK_ERRORS = {
107
+ "404_title": "Page Not Found",
108
+ "404_description": "The page you are looking for does not exist.",
109
+ "500_title": "Internal Server Error",
110
+ "500_description": "Something went wrong on our end. Please try again later."
111
+ } as const
112
+
113
+ export const FALLBACK_HOME = {
114
+ meta_title: "Daniel Marchi | Web Developer",
115
+ meta_description: "I'm a web developer specialized in Astro and high-performance websites.",
116
+ hero_title: "Hey, I'm Daniel Marchi",
117
+ hero_description: "I'm a web developer specialized in Astro and high-performance websites.",
118
+ hero_view_components: "View Components",
119
+ hero_contact_me: "Contact Me",
120
+ stack_title: "Modern Web, Modern Stack",
121
+ stack_description:
122
+ "I help individuals and companies build high-performance web applications deployed on the edge. From architecture to deployment, I ensure your project is built for scale.",
123
+ stack_view_services: "View Services",
124
+ stack_resume: "Resume",
125
+ marquee_title: "Built and deployed with these amazing technologies:",
126
+ projects_title: "Recent Projects",
127
+ projects_description: "A selection of my latest technical work and open source projects.",
128
+ projects_view_all: "View All Projects",
129
+ cta_title: "Ready to Build Something Amazing?",
130
+ cta_description:
131
+ "I'm currently available for new projects and consulting. Let's work together to bring your vision to life.",
132
+ cta_contact: "Get in Touch",
133
+ cta_resume: "Download Resume"
134
+ } as const
135
+
136
+ export const FALLBACK_MESSAGES = {
137
+ errors: {
138
+ ui: {
139
+ MISSING_TRANSLATED_UI:
140
+ "Sorry, this page is not yet fully available in your selected language.",
141
+ MISSING_TRANSLATED_CONTENT:
142
+ "Sorry, this article is not yet available in your selected language."
143
+ }
144
+ }
145
+ } as const
146
+
147
+ export const FALLBACK_NAV = {
148
+ home: "Home",
149
+ projects: "Projects",
150
+ blog: "Blog",
151
+ about: "About",
152
+ services: "Services",
153
+ resume: "Resume",
154
+ contact: "Contact",
155
+ components: "Components",
156
+ privacy_policy: "Privacy Policy",
157
+ terms_of_service: "Terms of Service",
158
+ cookie_policy: "Cookie Policy"
159
+ } as const
160
+
161
+ export const FALLBACK_PROJECTS = {
162
+ meta_title: "Projects | Daniel Marchi",
163
+ hero_title: "Projects",
164
+ hero_description: "A showcase of my work and open source contributions.",
165
+ latest: "Latest Project",
166
+ latest_badge: "Latest",
167
+ view_project: "View Project",
168
+ all: "All Projects"
169
+ } as const
170
+
171
+ export const FALLBACK_SERVICES = {
172
+ meta_title: "Services | Daniel Marchi",
173
+ hero_title: "Premium Services",
174
+ hero_description:
175
+ "I provide high-quality development and design services to help your business grow and stand out in the digital landscape.",
176
+ capabilities_title: "Capabilities",
177
+ capabilities_description: "An overview of the core services I provide to my clients.",
178
+ web_dev_title: "Web Development",
179
+ web_dev_description:
180
+ "Crafting high-performance, accessible, and scalable web applications using modern technologies like Astro, Vue, and Nuxt.",
181
+ ui_ux_title: "UI/UX Design",
182
+ ui_ux_description:
183
+ "Creating intuitive and visually stunning user interfaces focusing on user experience and brand identity.",
184
+ perf_title: "Performance Optimization",
185
+ perf_description:
186
+ "Audit and optimize your website for speed, SEO, and accessibility to achieve perfect Lighthouse scores.",
187
+ perf_security_title: "Performance & Security",
188
+ perf_security_description:
189
+ "Your users deserve a fast and secure experience. I build websites that load instantly and perform flawlessly across all devices.",
190
+ testimonials_title: "What Our Users Say",
191
+ testimonials_description:
192
+ "Join thousands of developers building the next generation of the web with Astronaut.",
193
+ faq_title: "Frequently Asked Questions",
194
+ faq_description:
195
+ "Everything you need to know about Astronaut and how to build amazing things with it.",
196
+ cta_title: "Ready to Start?",
197
+ cta_description:
198
+ "Let's collaborate on your next big project. I'm currently available for freelance work and consulting.",
199
+ cta_contact: "Get in Touch",
200
+ cta_portfolio: "View Portfolio",
201
+ badge_ceo: "CEO",
202
+ badge_founder: "Founder",
203
+ badge_cto: "CTO",
204
+ badge_senior_ui: "Senior UI Engineer",
205
+ badge_chef: "Chef",
206
+ badge_product_designer: "Product Designer",
207
+ testimonial_1:
208
+ "Astronaut is hands down the best boilerplate I've ever used. The speed and clean architecture matching perfectly with how I want to build.",
209
+ testimonial_2:
210
+ "The component library and the way it handles Tailwind 4 is a game changer. I was able to launch my project in record time.",
211
+ testimonial_3:
212
+ "Finally a boilerplate that doesn't feel bloated but gives me all the tools I need. The SSR support on Cloudflare is flawless.",
213
+ testimonial_4:
214
+ "The best starting point for any modern web project. The developer experience is world-class and the performance is unbeatable.",
215
+ testimonial_5:
216
+ "Incredible attention to detail in the structure. It follows all the best practices without adding unnecessary complexity.",
217
+ testimonial_6:
218
+ "Everything is so smooth! Transitions, components, logic - it all just clicks. Best decision for my latest startup.",
219
+ faq_category_general: "General",
220
+ faq_q1: "What is Astronaut?",
221
+ faq_a1:
222
+ "Astronaut is a production-ready boilerplate for Astro, designed with speed, performance, and best practices in mind. It uses Tailwind CSS 4, Nuxt UI patterns, and a clean architecture.",
223
+ faq_q2: "How do I get started?",
224
+ faq_a2:
225
+ "You can simply clone the repository, install dependencies with pnpm, and run the dev server. The project structure is standard Astro, so you'll feel right at home.",
226
+ faq_category_technical: "Technical",
227
+ faq_q3: "Does it support Server-Side Rendering (SSR)?",
228
+ faq_a3:
229
+ "Yes, Astronaut fully supports SSR and is configured for deployment on Cloudflare Workers by default, giving you the best of both worlds: static speed and dynamic power."
230
+ } as const
@@ -0,0 +1,268 @@
1
+ import { SITE_CONFIG } from "../../config"
2
+ import { TRANSLATIONS_VERSION } from "./runtime-constants"
3
+ let env: any = {}
4
+ try {
5
+ // @ts-ignore
6
+ const workers = await import(/* @vite-ignore */ "cloudflare:workers")
7
+ env = workers.env
8
+ } catch {
9
+ // Safe fallback for Node.js environments (Astro config, local scripts)
10
+ env = {}
11
+ }
12
+ import type { Locale, Namespace, PickSchema } from "./schema"
13
+ import { deepMerge } from "../../utils/shared/deep-merge"
14
+ import { parseBooleanFlag } from "../../utils/shared/parse-boolean"
15
+
16
+ // Optional fallbacks (auto-loaded if generated)
17
+ let FALLBACKS: Record<string, unknown> | null = null
18
+
19
+ try {
20
+ FALLBACKS = await import("./fallbacks.generated.ts")
21
+ } catch {
22
+ FALLBACKS = null
23
+ }
24
+
25
+ const KV_JSON_OPTIONS = { type: "json" as const, cacheTtl: 3600 }
26
+
27
+ function assertRecord(obj: unknown): asserts obj is Record<string, unknown> {
28
+ if (!obj || typeof obj !== "object") throw new Error("Expected record")
29
+ }
30
+
31
+ function assertDefaultCache(obj: unknown): asserts obj is {
32
+ match: (r: Request) => Promise<Response | undefined>
33
+ put: (r: Request, res: Response) => Promise<void>
34
+ } {
35
+ if (!obj || typeof obj !== "object") throw new Error("Expected cache")
36
+ }
37
+
38
+ function assertSchema<N extends Namespace>(obj: unknown): asserts obj is PickSchema<N> {
39
+ if (!obj) throw new Error("Expected schema")
40
+ }
41
+
42
+ function assertA<N extends Namespace>(obj: unknown): asserts obj is PickSchema<N> {
43
+ if (!obj) throw new Error("Expected result")
44
+ }
45
+
46
+ /**
47
+ * Runtime object provided by Astro via Astro.locals.
48
+ */
49
+ export interface Runtime {
50
+ cf?: unknown
51
+ cfContext: { waitUntil: (p: Promise<unknown>) => void }
52
+ }
53
+
54
+ /**
55
+ * Fetch translations from KV for a given locale and list of namespaces.
56
+ *
57
+ * Responsibilities:
58
+ *
59
+ * - Build KV keys using SITE_CONFIG.id, namespace and locale
60
+ * - Optionally use edge cache (controlled by env flag I18N_CACHE)
61
+ * - On cache miss, read from KV and merge with optional FALLBACK_* dictionaries
62
+ * - Persist the merged payload back to the edge cache
63
+ * - If KV is unavailable, fall back to static FALLBACK_* dictionaries only
64
+ */
65
+ export async function fetchTranslations<N extends Namespace>(
66
+ runtime: Runtime,
67
+ lang: Locale,
68
+ namespaces: readonly N[]
69
+ ): Promise<PickSchema<N>> {
70
+ const useCache = isCacheEnabled(env)
71
+
72
+ if (!useCache) {
73
+ debug(env, "i18n cache DISABLED, using KV only", { lang, namespaces })
74
+ }
75
+
76
+ const { cache, waitUntil } = useCache ? getEdgeCache(runtime) : { cache: null, waitUntil: null }
77
+
78
+ const keys = namespaces.map((ns) => `${SITE_CONFIG.id}:${ns}:${lang}`)
79
+
80
+ const { cacheRequest } = buildCacheKey(lang, namespaces)
81
+
82
+ // 1. Try to serve from edge cache if available and enabled
83
+ if (useCache && cache) {
84
+ try {
85
+ const cached = await cache.match(cacheRequest)
86
+
87
+ if (cached) {
88
+ const json = await cached.json()
89
+ assertSchema<N>(json)
90
+ debug(env, "i18n cache HIT", { lang, namespaces })
91
+ return json
92
+ }
93
+ debug(env, "i18n cache MISS (no entry)", { lang, namespaces })
94
+ } catch (error) {
95
+ debug(env, "i18n cache READ error, falling back to KV", error)
96
+ }
97
+ }
98
+
99
+ // 2. Cache is missing, disabled, or unavailable → read from KV
100
+ try {
101
+ const data = await loadTranslationsFromKV(env, keys, namespaces)
102
+ debug(env, "i18n KV OK", { lang, namespaces })
103
+
104
+ // 3. Store the resulting payload in the edge cache asynchronously (if enabled)
105
+ if (useCache && cache && waitUntil) {
106
+ try {
107
+ const response = new Response(JSON.stringify(data), {
108
+ headers: {
109
+ "Content-Type": "application/json; charset=utf-8",
110
+ "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400"
111
+ }
112
+ })
113
+
114
+ const putPromise = cache.put(cacheRequest, response.clone())
115
+
116
+ // In real Workers: schedule as a background task.
117
+ // In dev / non-Workers: just await so cache is actually written.
118
+ if (waitUntil) {
119
+ waitUntil(putPromise)
120
+ } else {
121
+ await putPromise
122
+ }
123
+
124
+ debug(env, "i18n cache PUT completed", { lang, namespaces })
125
+ } catch (error) {
126
+ debug(env, "i18n cache WRITE error", error)
127
+ }
128
+ }
129
+
130
+ return data
131
+ } catch (error) {
132
+ debug(env, "i18n KV ERROR, using FALLBACK_* only", error)
133
+
134
+ // 4. Hard fallback to static FALLBACK_* constants if KV is unavailable
135
+ return buildFallbackOnly(namespaces)
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Narrow runtime.caches and runtime.ctx to the minimal shape needed for using the Cache API and
141
+ * waitUntil in this module.
142
+ */
143
+ function getEdgeCache(runtime: Runtime): {
144
+ cache: {
145
+ match: (r: Request) => Promise<Response | undefined>
146
+ put: (r: Request, res: Response) => Promise<void>
147
+ } | null
148
+ waitUntil: ((p: Promise<unknown>) => void) | null
149
+ } {
150
+ return {
151
+ cache: getCacheFromStorage(),
152
+ waitUntil: runtime.cfContext?.waitUntil ?? null
153
+ }
154
+ }
155
+
156
+ function getCacheFromStorage(): {
157
+ match: (r: Request) => Promise<Response | undefined>
158
+ put: (r: Request, res: Response) => Promise<void>
159
+ } | null {
160
+ const c = Reflect.get(Object(caches), "default")
161
+ try {
162
+ assertDefaultCache(c)
163
+ return c
164
+ } catch {
165
+ return null
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Build a stable cache key and synthetic Request object used with the Cache API. The URL origin is
171
+ * irrelevant; only the string identity matters.
172
+ */
173
+ function buildCacheKey<N extends Namespace>(
174
+ lang: Locale,
175
+ namespaces: readonly N[]
176
+ ): { cacheId: string; cacheRequest: Request } {
177
+ const cacheId = `${SITE_CONFIG.id}:i18n:v${TRANSLATIONS_VERSION}:${lang}:${namespaces.join(",")}`
178
+
179
+ const cacheRequest = new Request(`https://i18n-cache/${encodeURIComponent(cacheId)}`)
180
+
181
+ return { cacheId, cacheRequest }
182
+ }
183
+
184
+ /**
185
+ * Read bulk translations from KV and merge with optional FALLBACK_* dictionaries. This is the main
186
+ * "happy-path" loader.
187
+ */
188
+ async function loadTranslationsFromKV<N extends Namespace>(
189
+ environment: Env,
190
+ keys: string[],
191
+ namespaces: readonly N[]
192
+ ): Promise<PickSchema<N>> {
193
+ const resultMap = await environment.TRANSLATIONS.get(keys, KV_JSON_OPTIONS)
194
+
195
+ const data: Partial<PickSchema<N>> = {}
196
+
197
+ for (let i = 0; i < namespaces.length; i++) {
198
+ const ns = namespaces[i]!
199
+ const key = keys[i]!
200
+ const value = resultMap.get(key) ?? {}
201
+ assertRecord(value)
202
+
203
+ const fallbackConstName = `FALLBACK_${String(ns).toUpperCase()}`
204
+ const fallback = FALLBACKS?.[fallbackConstName]
205
+
206
+ if (fallback) {
207
+ assertRecord(fallback)
208
+ Reflect.set(data, ns, deepMerge(fallback, value))
209
+ } else {
210
+ Reflect.set(data, ns, value)
211
+ }
212
+ }
213
+
214
+ assertA<N>(data)
215
+ return data
216
+ }
217
+
218
+ /**
219
+ * Build a payload using only FALLBACK_* dictionaries. Used when KV is unavailable or throws
220
+ * unexpectedly.
221
+ */
222
+ function buildFallbackOnly<N extends Namespace>(namespaces: readonly N[]): PickSchema<N> {
223
+ const empty: Partial<PickSchema<N>> = {}
224
+
225
+ for (let i = 0; i < namespaces.length; i++) {
226
+ const ns = namespaces[i]!
227
+ const fallbackConstName = `FALLBACK_${String(ns).toUpperCase()}`
228
+ const fallback = FALLBACKS?.[fallbackConstName]
229
+
230
+ ;(empty as Record<string, unknown>)[ns] = fallback ?? {}
231
+ }
232
+
233
+ assertSchema<N>(empty)
234
+ return empty
235
+ }
236
+
237
+ /**
238
+ * Determine whether edge caching is enabled.
239
+ *
240
+ * Wrangler var: I18N_CACHE = "on" | "off" | "true" | "false" | "1" | "0"
241
+ *
242
+ * After adding the variable:
243
+ *
244
+ * - Update wrangler.jsonc / wrangler.toml or .dev.vars
245
+ * - Run: npm run typegen
246
+ */
247
+ function isCacheEnabled(environment: Env): boolean {
248
+ // env.I18N_CACHE is generated by `wrangler types`
249
+ return parseBooleanFlag(environment.I18N_CACHE, /* defaultValue */ true)
250
+ }
251
+
252
+ /**
253
+ * Debug logger controlled by a Wrangler variable.
254
+ *
255
+ * Wrangler var: DEBUG_I18N = "on" | "off" | "true" | "false" | "1" | "0"
256
+ *
257
+ * After adding the variable:
258
+ *
259
+ * - Update wrangler.jsonc / wrangler.toml or .dev.vars
260
+ * - Run: npm run typegen
261
+ */
262
+ function debug(environment: Env, ...args: unknown[]) {
263
+ const enabled = parseBooleanFlag(environment.DEBUG_I18N, /* defaultValue */ false)
264
+ if (!enabled) return
265
+
266
+ // eslint-disable-next-line no-console
267
+ console.log(...args)
268
+ }
@@ -0,0 +1,16 @@
1
+ import type { Locale } from "./schema"
2
+
3
+ // Explicit locale → flag mapping
4
+ export const FLAG_CODES = {
5
+ en: "us-4x3",
6
+ ja: "jp-4x3",
7
+ de: "de-4x3",
8
+ es: "es-4x3",
9
+ pt: "br-4x3"
10
+ } as const
11
+
12
+ export type FlagCode = (typeof FLAG_CODES)[keyof typeof FLAG_CODES]
13
+
14
+ export const localeToFlagCode = (locale: Locale): FlagCode | null => {
15
+ return locale in FLAG_CODES ? FLAG_CODES[locale] : null
16
+ }