@rimelight/ui 0.0.5 → 0.0.7
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/LICENSE +21 -21
- package/package.json +22 -14
- package/src/components/BlogPost.astro +195 -0
- package/src/components/DynamicShowcase.astro +681 -0
- package/src/components/ExtendedCarousel.astro +107 -0
- package/src/components/FormattedDate.astro +6 -2
- package/src/components/Head.astro +153 -0
- package/src/components/ProjectPost.astro +197 -0
- package/src/components/RLHead.astro +3 -0
- package/src/components/ThemeToggle.astro +30 -42
- package/src/components/astro/RLAButton.astro +32 -42
- package/src/components/astro/RLAHeader.astro +75 -0
- package/src/components/nuxt/index.ts +1 -0
- package/src/components/vue/RLVButton.vue +50 -37
- package/src/components/vue/RLVHeader.vue +78 -0
- package/src/composables/index.ts +1 -1
- package/src/composables/{useComponentUI.ts → useUi.ts} +3 -3
- package/src/config/index.ts +0 -2
- package/src/config/site.config.ts +1 -1
- package/src/env.d.ts +13 -16
- package/src/integrations/ui.ts +106 -81
- package/src/integrations/vue-entrypoint.ts +6 -0
- package/src/middleware/sri.ts +2 -2
- package/src/styles/index.css +2 -0
- package/src/themes/button.theme.ts +350 -34
- package/src/themes/header.theme.ts +33 -0
- package/src/themes/index.ts +3 -1
- package/src/utils/index.ts +1 -2
- package/i18n-data.json +0 -186
- package/src/components/MissingTranslationBanner.astro +0 -39
- package/src/components/accordion/Accordion.astro +0 -257
- package/src/components/accordion/AccordionContent.astro +0 -34
- package/src/components/accordion/AccordionItem.astro +0 -29
- package/src/components/accordion/AccordionTrigger.astro +0 -33
- package/src/components/alert/Alert.astro +0 -34
- package/src/components/alert/AlertDescription.astro +0 -15
- package/src/components/alert/AlertTitle.astro +0 -17
- package/src/components/alert-dialog/AlertDialog.astro +0 -275
- package/src/components/alert-dialog/AlertDialogAction.astro +0 -46
- package/src/components/alert-dialog/AlertDialogCancel.astro +0 -46
- package/src/components/alert-dialog/AlertDialogContent.astro +0 -52
- package/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
- package/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
- package/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
- package/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
- package/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
- package/src/components/aspect-ratio/AspectRatio.astro +0 -32
- package/src/components/avatar/Avatar.astro +0 -29
- package/src/components/avatar/AvatarFallback.astro +0 -18
- package/src/components/avatar/AvatarImage.astro +0 -66
- package/src/components/badge/Badge.astro +0 -57
- package/src/components/breadcrumb/Breadcrumb.astro +0 -12
- package/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -29
- package/src/components/breadcrumb/BreadcrumbItem.astro +0 -15
- package/src/components/breadcrumb/BreadcrumbLink.astro +0 -24
- package/src/components/breadcrumb/BreadcrumbList.astro +0 -17
- package/src/components/breadcrumb/BreadcrumbPage.astro +0 -22
- package/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -24
- package/src/components/button/Button.astro +0 -58
- package/src/components/button-group/ButtonGroup.astro +0 -64
- package/src/components/button-group/ButtonGroupSeparator.astro +0 -20
- package/src/components/button-group/ButtonGroupText.astro +0 -19
- package/src/components/card/Card.astro +0 -36
- package/src/components/card/CardAction.astro +0 -16
- package/src/components/card/CardContent.astro +0 -16
- package/src/components/card/CardDescription.astro +0 -16
- package/src/components/card/CardFooter.astro +0 -16
- package/src/components/card/CardHeader.astro +0 -19
- package/src/components/card/CardTitle.astro +0 -16
- package/src/components/carousel/Carousel.astro +0 -55
- package/src/components/carousel/CarouselContent.astro +0 -26
- package/src/components/carousel/CarouselItem.astro +0 -26
- package/src/components/carousel/CarouselNext.astro +0 -39
- package/src/components/carousel/CarouselPrevious.astro +0 -41
- package/src/components/carousel/carousel-script.ts +0 -188
- package/src/components/carousel/index.ts +0 -1
- package/src/components/checkbox/Checkbox.astro +0 -133
- package/src/components/collapsible/Collapsible.astro +0 -161
- package/src/components/collapsible/CollapsibleContent.astro +0 -22
- package/src/components/collapsible/CollapsibleTrigger.astro +0 -44
- package/src/components/dialog/Dialog.astro +0 -389
- package/src/components/dialog/DialogClose.astro +0 -35
- package/src/components/dialog/DialogContent.astro +0 -78
- package/src/components/dialog/DialogDescription.astro +0 -14
- package/src/components/dialog/DialogFooter.astro +0 -14
- package/src/components/dialog/DialogHeader.astro +0 -14
- package/src/components/dialog/DialogTitle.astro +0 -18
- package/src/components/dialog/DialogTrigger.astro +0 -47
- package/src/components/dropdown/Dropdown.astro +0 -604
- package/src/components/dropdown/DropdownContent.astro +0 -118
- package/src/components/dropdown/DropdownItem.astro +0 -48
- package/src/components/dropdown/DropdownLabel.astro +0 -29
- package/src/components/dropdown/DropdownSeparator.astro +0 -21
- package/src/components/dropdown/DropdownShortcut.astro +0 -17
- package/src/components/dropdown/DropdownSub.astro +0 -15
- package/src/components/dropdown/DropdownSubContent.astro +0 -39
- package/src/components/dropdown/DropdownSubTrigger.astro +0 -34
- package/src/components/dropdown/DropdownTrigger.astro +0 -52
- package/src/components/dropzone/Dropzone.astro +0 -236
- package/src/components/dropzone/DropzoneFilesList.astro +0 -26
- package/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
- package/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
- package/src/components/embeds/YoutubeEmbed.astro +0 -94
- package/src/components/hero/Hero.astro +0 -166
- package/src/components/icons/Flag.astro +0 -62
- package/src/components/image/ExtendedImage.astro +0 -46
- package/src/components/image/Image.astro +0 -25
- package/src/components/input/Input.astro +0 -27
- package/src/components/input-group/InputGroup.astro +0 -28
- package/src/components/input-group/InputGroupAddon.astro +0 -76
- package/src/components/input-group/InputGroupButton.astro +0 -36
- package/src/components/input-group/InputGroupInput.astro +0 -18
- package/src/components/input-group/InputGroupText.astro +0 -17
- package/src/components/input-group/InputGroupTextarea.astro +0 -22
- package/src/components/input-otp/InputOtp.astro +0 -352
- package/src/components/input-otp/InputOtpGroup.astro +0 -16
- package/src/components/input-otp/InputOtpSeparator.astro +0 -25
- package/src/components/input-otp/InputOtpSlot.astro +0 -50
- package/src/components/input-otp/InputOtpTypes.ts +0 -6
- package/src/components/item/Item.astro +0 -52
- package/src/components/item/ItemActions.astro +0 -16
- package/src/components/item/ItemContent.astro +0 -16
- package/src/components/item/ItemDescription.astro +0 -19
- package/src/components/item/ItemFooter.astro +0 -16
- package/src/components/item/ItemGroup.astro +0 -16
- package/src/components/item/ItemHeader.astro +0 -16
- package/src/components/item/ItemMedia.astro +0 -40
- package/src/components/item/ItemSeparator.astro +0 -23
- package/src/components/item/ItemTitle.astro +0 -16
- package/src/components/kbd/Kbd.astro +0 -21
- package/src/components/kbd/KbdGroup.astro +0 -16
- package/src/components/label/Label.astro +0 -24
- package/src/components/marquee/Marquee.astro +0 -73
- package/src/components/native-select/NativeSelect.astro +0 -68
- package/src/components/native-select/NativeSelectOptGroup.astro +0 -15
- package/src/components/native-select/NativeSelectOption.astro +0 -15
- package/src/components/pagination/Pagination.astro +0 -20
- package/src/components/pagination/PaginationContent.astro +0 -16
- package/src/components/pagination/PaginationEllipsis.astro +0 -37
- package/src/components/pagination/PaginationItem.astro +0 -16
- package/src/components/pagination/PaginationLink.astro +0 -27
- package/src/components/pagination/PaginationNext.astro +0 -33
- package/src/components/pagination/PaginationPrevious.astro +0 -33
- package/src/components/popover/Popover.astro +0 -717
- package/src/components/popover/PopoverContent.astro +0 -102
- package/src/components/popover/PopoverDescription.astro +0 -14
- package/src/components/popover/PopoverHeader.astro +0 -14
- package/src/components/popover/PopoverTitle.astro +0 -14
- package/src/components/popover/PopoverTrigger.astro +0 -51
- package/src/components/progress/Progress.astro +0 -154
- package/src/components/prose/Prose.astro +0 -619
- package/src/components/radio-group/RadioGroup.astro +0 -162
- package/src/components/radio-group/RadioGroupItem.astro +0 -133
- package/src/components/radio-group/RadioGroupTypes.ts +0 -6
- package/src/components/section/Section.astro +0 -167
- package/src/components/select/Select.astro +0 -752
- package/src/components/select/SelectContent.astro +0 -94
- package/src/components/select/SelectGroup.astro +0 -9
- package/src/components/select/SelectItem.astro +0 -51
- package/src/components/select/SelectLabel.astro +0 -14
- package/src/components/select/SelectSearch.astro +0 -49
- package/src/components/select/SelectSeparator.astro +0 -12
- package/src/components/select/SelectTrigger.astro +0 -56
- package/src/components/select/SelectTypes.ts +0 -13
- package/src/components/select/SelectValue.astro +0 -19
- package/src/components/separator/Separator.astro +0 -36
- package/src/components/sheet/Sheet.astro +0 -15
- package/src/components/sheet/SheetClose.astro +0 -15
- package/src/components/sheet/SheetContent.astro +0 -89
- package/src/components/sheet/SheetDescription.astro +0 -16
- package/src/components/sheet/SheetFooter.astro +0 -16
- package/src/components/sheet/SheetHeader.astro +0 -16
- package/src/components/sheet/SheetTitle.astro +0 -16
- package/src/components/sheet/SheetTrigger.astro +0 -15
- package/src/components/sidebar/Sidebar.astro +0 -231
- package/src/components/sidebar/SidebarContent.astro +0 -25
- package/src/components/sidebar/SidebarFooter.astro +0 -22
- package/src/components/sidebar/SidebarGroup.astro +0 -22
- package/src/components/sidebar/SidebarGroupContent.astro +0 -22
- package/src/components/sidebar/SidebarGroupLabel.astro +0 -53
- package/src/components/sidebar/SidebarHeader.astro +0 -22
- package/src/components/sidebar/SidebarInput.astro +0 -24
- package/src/components/sidebar/SidebarInset.astro +0 -22
- package/src/components/sidebar/SidebarMenu.astro +0 -22
- package/src/components/sidebar/SidebarMenuAction.astro +0 -63
- package/src/components/sidebar/SidebarMenuBadge.astro +0 -31
- package/src/components/sidebar/SidebarMenuButton.astro +0 -124
- package/src/components/sidebar/SidebarMenuItem.astro +0 -22
- package/src/components/sidebar/SidebarMenuSkeleton.astro +0 -43
- package/src/components/sidebar/SidebarMenuSub.astro +0 -25
- package/src/components/sidebar/SidebarMenuSubButton.astro +0 -52
- package/src/components/sidebar/SidebarMenuSubItem.astro +0 -17
- package/src/components/sidebar/SidebarProvider.astro +0 -215
- package/src/components/sidebar/SidebarRail.astro +0 -72
- package/src/components/sidebar/SidebarSeparator.astro +0 -25
- package/src/components/sidebar/SidebarTrigger.astro +0 -69
- package/src/components/skeleton/Skeleton.astro +0 -14
- package/src/components/slider/Slider.astro +0 -425
- package/src/components/spinner/Spinner.astro +0 -23
- package/src/components/switch/Switch.astro +0 -192
- package/src/components/switch/SwitchTypes.ts +0 -6
- package/src/components/table/Table.astro +0 -20
- package/src/components/table/TableBody.astro +0 -18
- package/src/components/table/TableCaption.astro +0 -18
- package/src/components/table/TableCell.astro +0 -18
- package/src/components/table/TableFoot.astro +0 -18
- package/src/components/table/TableHead.astro +0 -18
- package/src/components/table/TableHeader.astro +0 -18
- package/src/components/table/TableRow.astro +0 -17
- package/src/components/tabs/Tabs.astro +0 -270
- package/src/components/tabs/TabsContent.astro +0 -28
- package/src/components/tabs/TabsList.astro +0 -23
- package/src/components/tabs/TabsTrigger.astro +0 -34
- package/src/components/textarea/Textarea.astro +0 -31
- package/src/components/toast/ToastDescription.astro +0 -22
- package/src/components/toast/ToastItem.astro +0 -56
- package/src/components/toast/ToastTemplate.astro +0 -27
- package/src/components/toast/ToastTitle.astro +0 -59
- package/src/components/toast/Toaster.astro +0 -981
- package/src/components/toast/index.ts +0 -1
- package/src/components/toast/toast-manager.ts +0 -234
- package/src/components/toggle/Toggle.astro +0 -174
- package/src/components/toggle/ToggleTypes.ts +0 -14
- package/src/components/tooltip/Tooltip.astro +0 -422
- package/src/components/tooltip/TooltipContent.astro +0 -83
- package/src/components/tooltip/TooltipTrigger.astro +0 -13
- package/src/components/video/Video.astro +0 -121
- package/src/config/cookies.ts +0 -5
- package/src/config/links.ts +0 -4
- package/src/domain/cms/index.ts +0 -1
- package/src/domain/cms/types.ts +0 -20
- package/src/domain/i18n/constants.ts +0 -21
- package/src/domain/i18n/cookie-storage.ts +0 -13
- package/src/domain/i18n/country-to-locale-map.ts +0 -67
- package/src/domain/i18n/fallbacks.generated.ts +0 -230
- package/src/domain/i18n/fetcher.ts +0 -278
- package/src/domain/i18n/flags.ts +0 -16
- package/src/domain/i18n/format.ts +0 -90
- package/src/domain/i18n/index.ts +0 -10
- package/src/domain/i18n/middleware/i18n.ts +0 -161
- package/src/domain/i18n/resolve-locale.ts +0 -55
- package/src/domain/i18n/runtime-constants.ts +0 -14
- package/src/domain/i18n/schema.ts +0 -29
- package/src/domain/index.ts +0 -4
- package/src/domain/seo/constants.ts +0 -6
- package/src/domain/seo/index.ts +0 -3
- package/src/domain/seo/services/index.ts +0 -3
- package/src/domain/seo/services/llms.ts +0 -56
- package/src/domain/seo/services/robots.ts +0 -12
- package/src/domain/seo/services/sitemap.ts +0 -56
- package/src/domain/seo/utils.ts +0 -36
- package/src/domain/theme/constants.ts +0 -6
- package/src/domain/theme/index.ts +0 -2
- package/src/domain/theme/schema.ts +0 -10
- package/src/i18n.generated.d.ts +0 -202
- package/src/lib/component-preview.ts +0 -73
- package/src/lib/index.ts +0 -6
- package/src/lib/remark/remark-modified-time.ts +0 -13
- package/src/lib/showcase-preview-elements.ts +0 -408
- package/src/lib/showcase-renderer.ts +0 -218
- package/src/lib/showcase-utils.ts +0 -189
- package/src/lib/turnstile.ts +0 -35
- package/src/lib/utils/starwind/positioning.ts +0 -318
- package/src/locales/de/blog.json +0 -9
- package/src/locales/de/common.json +0 -19
- package/src/locales/de/errors.json +0 -6
- package/src/locales/de/home.json +0 -20
- package/src/locales/de/messages.json +0 -8
- package/src/locales/de/nav.json +0 -13
- package/src/locales/de/projects.json +0 -9
- package/src/locales/de/services.json +0 -43
- package/src/locales/en/blog.json +0 -9
- package/src/locales/en/chat.json +0 -29
- package/src/locales/en/common.json +0 -19
- package/src/locales/en/cookie_notice.json +0 -11
- package/src/locales/en/editor.json +0 -24
- package/src/locales/en/errors.json +0 -6
- package/src/locales/en/home.json +0 -20
- package/src/locales/en/messages.json +0 -8
- package/src/locales/en/nav.json +0 -13
- package/src/locales/en/projects.json +0 -9
- package/src/locales/en/services.json +0 -43
- package/src/locales/es/blog.json +0 -9
- package/src/locales/es/common.json +0 -19
- package/src/locales/es/errors.json +0 -6
- package/src/locales/es/home.json +0 -20
- package/src/locales/es/messages.json +0 -8
- package/src/locales/es/nav.json +0 -13
- package/src/locales/es/projects.json +0 -9
- package/src/locales/es/services.json +0 -43
- package/src/locales/ja/blog.json +0 -9
- package/src/locales/ja/common.json +0 -19
- package/src/locales/ja/errors.json +0 -6
- package/src/locales/ja/home.json +0 -20
- package/src/locales/ja/messages.json +0 -8
- package/src/locales/ja/nav.json +0 -13
- package/src/locales/ja/projects.json +0 -9
- package/src/locales/ja/services.json +0 -43
- package/src/locales/pt/blog.json +0 -9
- package/src/locales/pt/chat.json +0 -29
- package/src/locales/pt/common.json +0 -19
- package/src/locales/pt/cookie_notice.json +0 -11
- package/src/locales/pt/editor.json +0 -24
- package/src/locales/pt/errors.json +0 -6
- package/src/locales/pt/home.json +0 -20
- package/src/locales/pt/messages.json +0 -8
- package/src/locales/pt/nav.json +0 -13
- package/src/locales/pt/projects.json +0 -9
- package/src/locales/pt/services.json +0 -43
- package/src/utils/server/cookies.ts +0 -98
- package/src/utils/server/index.ts +0 -1
- package/src/utils/shared/deep-merge.ts +0 -36
- package/src/utils/shared/index.ts +0 -3
- package/src/utils/shared/parse-boolean.ts +0 -14
- /package/src/utils/{shared/embed.ts → embed.ts} +0 -0
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,230 +0,0 @@
|
|
|
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
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { TRANSLATIONS_VERSION } from "./runtime-constants"
|
|
2
|
-
let env: any = {}
|
|
3
|
-
try {
|
|
4
|
-
// @ts-ignore
|
|
5
|
-
const workers = await import(/* @vite-ignore */ "cloudflare:workers")
|
|
6
|
-
env = workers.env
|
|
7
|
-
} catch {
|
|
8
|
-
// Safe fallback for Node.js environments (Astro config, local scripts)
|
|
9
|
-
env = {}
|
|
10
|
-
}
|
|
11
|
-
import type { Locale, Namespace, PickSchema } from "./schema"
|
|
12
|
-
import { deepMerge } from "../../utils/shared/deep-merge"
|
|
13
|
-
import { parseBooleanFlag } from "../../utils/shared/parse-boolean"
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Minimal environment interface for i18n operations. Usually provided by Cloudflare Workers.
|
|
17
|
-
*/
|
|
18
|
-
interface Env {
|
|
19
|
-
TRANSLATIONS: {
|
|
20
|
-
get: (keys: string[], options: { type: "json"; cacheTtl?: number }) => Promise<Map<string, any>>
|
|
21
|
-
}
|
|
22
|
-
I18N_CACHE?: string
|
|
23
|
-
DEBUG_I18N?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Optional fallbacks (auto-loaded if generated)
|
|
27
|
-
let FALLBACKS: Record<string, unknown> | null = null
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
FALLBACKS = await import("./fallbacks.generated.ts")
|
|
31
|
-
} catch {
|
|
32
|
-
FALLBACKS = null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const KV_JSON_OPTIONS = { type: "json" as const, cacheTtl: 3600 }
|
|
36
|
-
|
|
37
|
-
function assertRecord(obj: unknown): asserts obj is Record<string, unknown> {
|
|
38
|
-
if (!obj || typeof obj !== "object") throw new Error("Expected record")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function assertDefaultCache(obj: unknown): asserts obj is {
|
|
42
|
-
match: (r: Request) => Promise<Response | undefined>
|
|
43
|
-
put: (r: Request, res: Response) => Promise<void>
|
|
44
|
-
} {
|
|
45
|
-
if (!obj || typeof obj !== "object") throw new Error("Expected cache")
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function assertSchema<N extends Namespace>(obj: unknown): asserts obj is PickSchema<N> {
|
|
49
|
-
if (!obj) throw new Error("Expected schema")
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function assertA<N extends Namespace>(obj: unknown): asserts obj is PickSchema<N> {
|
|
53
|
-
if (!obj) throw new Error("Expected result")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Runtime object provided by Astro via Astro.locals.
|
|
58
|
-
*/
|
|
59
|
-
export interface Runtime {
|
|
60
|
-
cf?: unknown
|
|
61
|
-
cfContext: { waitUntil: (p: Promise<unknown>) => void }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Fetch translations from KV for a given locale and list of namespaces.
|
|
66
|
-
*
|
|
67
|
-
* Responsibilities:
|
|
68
|
-
*
|
|
69
|
-
* - Build KV keys using namespace and locale (isolated by KV namespace binding)
|
|
70
|
-
* - Optionally use edge cache (controlled by env flag I18N_CACHE)
|
|
71
|
-
* - On cache miss, read from KV and merge with optional FALLBACK_* dictionaries
|
|
72
|
-
* - Persist the merged payload back to the edge cache
|
|
73
|
-
* - If KV is unavailable, fall back to static FALLBACK_* dictionaries only
|
|
74
|
-
*/
|
|
75
|
-
export async function fetchTranslations<N extends Namespace>(
|
|
76
|
-
runtime: Runtime,
|
|
77
|
-
lang: Locale,
|
|
78
|
-
namespaces: readonly N[]
|
|
79
|
-
): Promise<PickSchema<N>> {
|
|
80
|
-
const useCache = isCacheEnabled(env)
|
|
81
|
-
|
|
82
|
-
if (!useCache) {
|
|
83
|
-
debug(env, "i18n cache DISABLED, using KV only", { lang, namespaces })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { cache, waitUntil } = useCache ? getEdgeCache(runtime) : { cache: null, waitUntil: null }
|
|
87
|
-
|
|
88
|
-
const keys = namespaces.map((ns) => `${ns}:${lang}`)
|
|
89
|
-
|
|
90
|
-
const { cacheRequest } = buildCacheKey(lang, namespaces)
|
|
91
|
-
|
|
92
|
-
// 1. Try to serve from edge cache if available and enabled
|
|
93
|
-
if (useCache && cache) {
|
|
94
|
-
try {
|
|
95
|
-
const cached = await cache.match(cacheRequest)
|
|
96
|
-
|
|
97
|
-
if (cached) {
|
|
98
|
-
const json = await cached.json()
|
|
99
|
-
assertSchema<N>(json)
|
|
100
|
-
debug(env, "i18n cache HIT", { lang, namespaces })
|
|
101
|
-
return json
|
|
102
|
-
}
|
|
103
|
-
debug(env, "i18n cache MISS (no entry)", { lang, namespaces })
|
|
104
|
-
} catch (error) {
|
|
105
|
-
debug(env, "i18n cache READ error, falling back to KV", error)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 2. Cache is missing, disabled, or unavailable → read from KV
|
|
110
|
-
try {
|
|
111
|
-
const data = await loadTranslationsFromKV(env, keys, namespaces)
|
|
112
|
-
debug(env, "i18n KV OK", { lang, namespaces })
|
|
113
|
-
|
|
114
|
-
// 3. Store the resulting payload in the edge cache asynchronously (if enabled)
|
|
115
|
-
if (useCache && cache && waitUntil) {
|
|
116
|
-
try {
|
|
117
|
-
const response = new Response(JSON.stringify(data), {
|
|
118
|
-
headers: {
|
|
119
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
120
|
-
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400"
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const putPromise = cache.put(cacheRequest, response.clone())
|
|
125
|
-
|
|
126
|
-
// In real Workers: schedule as a background task.
|
|
127
|
-
// In dev / non-Workers: just await so cache is actually written.
|
|
128
|
-
if (waitUntil) {
|
|
129
|
-
waitUntil(putPromise)
|
|
130
|
-
} else {
|
|
131
|
-
await putPromise
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
debug(env, "i18n cache PUT completed", { lang, namespaces })
|
|
135
|
-
} catch (error) {
|
|
136
|
-
debug(env, "i18n cache WRITE error", error)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return data
|
|
141
|
-
} catch (error) {
|
|
142
|
-
debug(env, "i18n KV ERROR, using FALLBACK_* only", error)
|
|
143
|
-
|
|
144
|
-
// 4. Hard fallback to static FALLBACK_* constants if KV is unavailable
|
|
145
|
-
return buildFallbackOnly(namespaces)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Narrow runtime.caches and runtime.ctx to the minimal shape needed for using the Cache API and
|
|
151
|
-
* waitUntil in this module.
|
|
152
|
-
*/
|
|
153
|
-
function getEdgeCache(runtime: Runtime): {
|
|
154
|
-
cache: {
|
|
155
|
-
match: (r: Request) => Promise<Response | undefined>
|
|
156
|
-
put: (r: Request, res: Response) => Promise<void>
|
|
157
|
-
} | null
|
|
158
|
-
waitUntil: ((p: Promise<unknown>) => void) | null
|
|
159
|
-
} {
|
|
160
|
-
return {
|
|
161
|
-
cache: getCacheFromStorage(),
|
|
162
|
-
waitUntil: runtime.cfContext?.waitUntil ?? null
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function getCacheFromStorage(): {
|
|
167
|
-
match: (r: Request) => Promise<Response | undefined>
|
|
168
|
-
put: (r: Request, res: Response) => Promise<void>
|
|
169
|
-
} | null {
|
|
170
|
-
const c = Reflect.get(Object(caches), "default")
|
|
171
|
-
try {
|
|
172
|
-
assertDefaultCache(c)
|
|
173
|
-
return c
|
|
174
|
-
} catch {
|
|
175
|
-
return null
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Build a stable cache key and synthetic Request object used with the Cache API. The URL origin is
|
|
181
|
-
* irrelevant; only the string identity matters.
|
|
182
|
-
*/
|
|
183
|
-
function buildCacheKey<N extends Namespace>(
|
|
184
|
-
lang: Locale,
|
|
185
|
-
namespaces: readonly N[]
|
|
186
|
-
): { cacheId: string; cacheRequest: Request } {
|
|
187
|
-
const cacheId = `i18n:v${TRANSLATIONS_VERSION}:${lang}:${namespaces.join(",")}`
|
|
188
|
-
|
|
189
|
-
const cacheRequest = new Request(`https://i18n-cache/${encodeURIComponent(cacheId)}`)
|
|
190
|
-
|
|
191
|
-
return { cacheId, cacheRequest }
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Read bulk translations from KV and merge with optional FALLBACK_* dictionaries. This is the main
|
|
196
|
-
* "happy-path" loader.
|
|
197
|
-
*/
|
|
198
|
-
async function loadTranslationsFromKV<N extends Namespace>(
|
|
199
|
-
environment: Env,
|
|
200
|
-
keys: string[],
|
|
201
|
-
namespaces: readonly N[]
|
|
202
|
-
): Promise<PickSchema<N>> {
|
|
203
|
-
const resultMap = await environment.TRANSLATIONS.get(keys, KV_JSON_OPTIONS)
|
|
204
|
-
|
|
205
|
-
const data: Partial<PickSchema<N>> = {}
|
|
206
|
-
|
|
207
|
-
for (let i = 0; i < namespaces.length; i++) {
|
|
208
|
-
const ns = namespaces[i]!
|
|
209
|
-
const key = keys[i]!
|
|
210
|
-
const value = resultMap.get(key) ?? {}
|
|
211
|
-
assertRecord(value)
|
|
212
|
-
|
|
213
|
-
const fallbackConstName = `FALLBACK_${String(ns).toUpperCase()}`
|
|
214
|
-
const fallback = FALLBACKS?.[fallbackConstName]
|
|
215
|
-
|
|
216
|
-
if (fallback) {
|
|
217
|
-
assertRecord(fallback)
|
|
218
|
-
Reflect.set(data, ns, deepMerge(fallback, value))
|
|
219
|
-
} else {
|
|
220
|
-
Reflect.set(data, ns, value)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
assertA<N>(data)
|
|
225
|
-
return data
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Build a payload using only FALLBACK_* dictionaries. Used when KV is unavailable or throws
|
|
230
|
-
* unexpectedly.
|
|
231
|
-
*/
|
|
232
|
-
function buildFallbackOnly<N extends Namespace>(namespaces: readonly N[]): PickSchema<N> {
|
|
233
|
-
const empty: Partial<PickSchema<N>> = {}
|
|
234
|
-
|
|
235
|
-
for (let i = 0; i < namespaces.length; i++) {
|
|
236
|
-
const ns = namespaces[i]!
|
|
237
|
-
const fallbackConstName = `FALLBACK_${String(ns).toUpperCase()}`
|
|
238
|
-
const fallback = FALLBACKS?.[fallbackConstName]
|
|
239
|
-
|
|
240
|
-
;(empty as Record<string, unknown>)[ns] = fallback ?? {}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
assertSchema<N>(empty)
|
|
244
|
-
return empty
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Determine whether edge caching is enabled.
|
|
249
|
-
*
|
|
250
|
-
* Wrangler var: I18N_CACHE = "on" | "off" | "true" | "false" | "1" | "0"
|
|
251
|
-
*
|
|
252
|
-
* After adding the variable:
|
|
253
|
-
*
|
|
254
|
-
* - Update wrangler.jsonc / wrangler.toml or .dev.vars
|
|
255
|
-
* - Run: npm run typegen
|
|
256
|
-
*/
|
|
257
|
-
function isCacheEnabled(environment: Env): boolean {
|
|
258
|
-
// env.I18N_CACHE is generated by `wrangler types`
|
|
259
|
-
return parseBooleanFlag(environment.I18N_CACHE, /* defaultValue */ true)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Debug logger controlled by a Wrangler variable.
|
|
264
|
-
*
|
|
265
|
-
* Wrangler var: DEBUG_I18N = "on" | "off" | "true" | "false" | "1" | "0"
|
|
266
|
-
*
|
|
267
|
-
* After adding the variable:
|
|
268
|
-
*
|
|
269
|
-
* - Update wrangler.jsonc / wrangler.toml or .dev.vars
|
|
270
|
-
* - Run: npm run typegen
|
|
271
|
-
*/
|
|
272
|
-
function debug(environment: Env, ...args: unknown[]) {
|
|
273
|
-
const enabled = parseBooleanFlag(environment.DEBUG_I18N, /* defaultValue */ false)
|
|
274
|
-
if (!enabled) return
|
|
275
|
-
|
|
276
|
-
// eslint-disable-next-line no-console
|
|
277
|
-
console.log(...args)
|
|
278
|
-
}
|
package/src/domain/i18n/flags.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// Sanitizer: Turns <script> into <script>
|
|
2
|
-
function escapeHtml(unsafe: string | number): string {
|
|
3
|
-
return String(unsafe)
|
|
4
|
-
.replace(/&/g, "&")
|
|
5
|
-
.replace(/</g, "<")
|
|
6
|
-
.replace(/>/g, ">")
|
|
7
|
-
.replace(/"/g, """)
|
|
8
|
-
.replace(/'/g, "'")
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type TemplateValues = Record<string, string | number>
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Interpolates {placeholders} in a template string and escapes values. - If `template` is
|
|
15
|
-
* undefined/null, returns an empty string (and logs in dev). - If `values` is undefined, returns
|
|
16
|
-
* the template unchanged.
|
|
17
|
-
*/
|
|
18
|
-
export function fmt(template: string | null | undefined, values?: TemplateValues): string {
|
|
19
|
-
if (template == null) {
|
|
20
|
-
if (!import.meta.env.PROD) {
|
|
21
|
-
console.warn("[i18n] fmt() called with null/undefined template")
|
|
22
|
-
}
|
|
23
|
-
return ""
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!values) return template
|
|
27
|
-
|
|
28
|
-
return template.replace(/\{([^}]+)\}/g, (match, key) => {
|
|
29
|
-
const replacement = values[key]
|
|
30
|
-
|
|
31
|
-
if (replacement === undefined || replacement === null) {
|
|
32
|
-
return match
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return escapeHtml(replacement)
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Simple two-form plural helper. Good for English-like languages where "1" vs "other" is enough.
|
|
41
|
-
*/
|
|
42
|
-
export function plural(count: number, singular: string, pluralForm: string): string {
|
|
43
|
-
return count === 1 ? singular : fmt(pluralForm, { count })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* ICU-style plural categories used by Intl.PluralRules.
|
|
48
|
-
*/
|
|
49
|
-
export type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other"
|
|
50
|
-
|
|
51
|
-
export type PluralPatterns = Partial<Record<PluralCategory, string>>
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Advanced plural helper using Intl.PluralRules + fmt().
|
|
55
|
-
*
|
|
56
|
-
* - `locale` controls plural categories (en, de, es, ja, ...).
|
|
57
|
-
* - `patterns` comes from KV (e.g. { zero, one, other }).
|
|
58
|
-
* - Missing categories fall back to `other`.
|
|
59
|
-
*
|
|
60
|
-
* Example: pluralIcu(5, 'en', { zero: 'No items', one: '{count} item', other: '{count} items' })
|
|
61
|
-
*/
|
|
62
|
-
export function pluralIcu(count: number, locale: string, patterns: PluralPatterns): string {
|
|
63
|
-
const rules = new Intl.PluralRules(locale)
|
|
64
|
-
const cat = rules.select(count) as PluralCategory
|
|
65
|
-
|
|
66
|
-
const template =
|
|
67
|
-
patterns[cat] ??
|
|
68
|
-
patterns.other ??
|
|
69
|
-
(import.meta.env.PROD ? "" : (console.warn('[i18n] pluralIcu(): missing "other" pattern'), ""))
|
|
70
|
-
|
|
71
|
-
return fmt(template, { count })
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Used in the LanguageSwitcher and SeoHreflangs components
|
|
75
|
-
export function replaceLocaleInPath(
|
|
76
|
-
currentPath: string,
|
|
77
|
-
currentLocale: string,
|
|
78
|
-
newLocale: string
|
|
79
|
-
): string {
|
|
80
|
-
const prefixRegex = new RegExp(`^/${currentLocale}/`)
|
|
81
|
-
return currentPath.replace(prefixRegex, `/${newLocale}/`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Used in the LanguageSwitcher component
|
|
85
|
-
export function getLangFromUrl(pathname: string) {
|
|
86
|
-
// Remove leading/trailing slashes: '/en/about/' -> 'en/about'
|
|
87
|
-
const cleaned = pathname.replace(/^\/+|\/+$/g, "")
|
|
88
|
-
const [lang] = cleaned.split("/")
|
|
89
|
-
return lang
|
|
90
|
-
}
|