{"id":13417823,"url":"https://github.com/alexandre-fernandez/astro-i18n","last_synced_at":"2025-04-04T20:04:03.107Z","repository":{"id":257778997,"uuid":"566480850","full_name":"Alexandre-Fernandez/astro-i18n","owner":"Alexandre-Fernandez","description":"A TypeScript-first internationalization library for Astro.","archived":false,"fork":false,"pushed_at":"2024-06-20T09:27:59.000Z","size":1139,"stargazers_count":257,"open_issues_count":13,"forks_count":11,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-28T14:09:28.240Z","etag":null,"topics":["astro","i18n","internationalization","library","localization","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/astro-i18n","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Alexandre-Fernandez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["alexandre-fernandez"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2022-11-15T19:06:40.000Z","updated_at":"2025-03-26T11:18:13.000Z","dependencies_parsed_at":"2024-09-19T11:16:48.889Z","dependency_job_id":"444f4816-b92e-419c-b6d5-484f828ab566","html_url":"https://github.com/Alexandre-Fernandez/astro-i18n","commit_stats":{"total_commits":159,"total_committers":4,"mean_commits":39.75,"dds":0.08176100628930816,"last_synced_commit":"bcc58d831109b34c3229d9b0de2300e0714428e0"},"previous_names":["alexandre-fernandez/astro-i18n"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexandre-Fernandez%2Fastro-i18n","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexandre-Fernandez%2Fastro-i18n/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexandre-Fernandez%2Fastro-i18n/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alexandre-Fernandez%2Fastro-i18n/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Alexandre-Fernandez","download_url":"https://codeload.github.com/Alexandre-Fernandez/astro-i18n/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246042003,"owners_count":20714147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astro","i18n","internationalization","library","localization","typescript"],"created_at":"2024-07-30T22:00:53.432Z","updated_at":"2025-03-28T19:05:47.073Z","avatar_url":"https://github.com/Alexandre-Fernandez.png","language":"TypeScript","funding_links":["https://github.com/sponsors/alexandre-fernandez"],"categories":["What Do I Use..."],"sub_categories":["If I want to add internationalization?"],"readme":"\u003cdiv align=\"center\" \u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/Alexandre-Fernandez/astro-i18n/0fb145cf64ef94968ca70c36b52a922fa897d11a/img/logo.svg\" alt=\"astro-i18n logo\" width=\"33%\"\u003e\n    \u003cbr/\u003e\n\t\u003ch1\u003eastro-i18n\u003c/h1\u003e\n    \u003cp\u003eA TypeScript-first internationalization library for \u003ca href=\"https://github.com/withastro/astro\"\u003eAstro\u003c/a\u003e.\u003c/p\u003e\n\u003c/div\u003e\n\n## Features\n\n-   🪶 Lightweight.\n-   🌏 Internationalized routing.\n-   🔒 Type-safety and autocompletion.\n-   🔧 Support for plurals, context, interpolations and formatters.\n-   🚀 Built for [Astro](https://github.com/withastro/astro) and nothing else.\n-   💻 Astro island compatible.\n\n## Introduction\n\nMost internationalization libraries rely on raw untyped strings to access their translations. This is equally possible with astro-i18n however it can generate project specific types to get type-safety and autocompletion for your translations.\n\n## Installation\n\n```yml\n# npm\nnpm install astro-i18n\n# yarn\nyarn add astro-i18n\n# pnpm\npnpm add astro-i18n\n```\n\n## Get started\n\nRun the quick install command :\n\n```yml\n./node_modules/.bin/astro-i18n install\n# serverless (no filesystem/node.js setup)\n./node_modules/.bin/astro-i18n install --serverless\n```\n\nVerify that your middleware file at `src/middleware.ts` or `src/middleware/index.ts` uses the `astro-i18n` middleware. If the file was generated by the quick install command it should be fine.\n\n```ts\n// src/middleware.ts\nimport { sequence } from \"astro/middleware\"\nimport { useAstroI18n } from \"astro-i18n\"\n\nconst astroI18n = useAstroI18n(/* astro-i18n config, custom formatters */)\n\nexport const onRequest = sequence(astroI18n)\n```\n\nIf you are using **serverless all your initial data needs to be passed to the middleware**, you should import your root astro-i18n config and pass it to the middleware.\n\nWhen using node (SSR or SSG) you can either go for the same workflow as serverless or use the filesystem to store your config and translations.\n\nIf you used the quick install (with node) a config file for `astro-i18n` should have been generated, you will find it in your root directory (`astro-i18n.config.(ts|js|cjs|mjs|json)`). This is the same as passing it in the middleware parameters.\n**Configure it as needed, you can set your `primaryLocale` and `secondaryLocales` there**.\n\nYou can put your translations in four locations :\n\n-   Inside the middleware config.\n-   Inside the config file at the root.\n-   In your `src/pages` folder (local page translations), see below.\n-   In the `src/i18n` directory, see below.\n\n**`astro-i18n` works on the server-side and on the client-side** so that it can work with client-side frameworks. Because of this **translations are aggregated into groups**:\n\n-   The `\"common\"` group which is accesible on every page.\n-   The current page group named after its route, such as `\"/about\"` or `\"/posts/[id]\"`, as its name implies it's only accessible on the current page.\n-   Any other custom group named by yourself, for example `\"admin\"`. It's up to you to decide on which pages your custom group is accessible.\n\n**By default any translation accessible on the current page is visible on the client-side** (you may change that in the config).\n\n![Filesystem translations screenshot](https://raw.githubusercontent.com/Alexandre-Fernandez/astro-i18n/2.0/img/json_translations.png \"Filesystem translations screenshot\")\n\nAs shown in the screenshot above, local page translations can be either in `src/pages` or `src/i18n/pages`, nest them just like you do with astro file-based routing.\nAll the following translation file formats are valid :\n\n```ts\nconst regular = `${locale}.json` // fr.json\nconst private = `_${locale}.json` // _fr.json\nconst regularWithTranslation = `${locale}.${segmentTranslation}.json` // fr.translated-page-name.json\nconst privateWithTranslation = `_${locale}.${segmentTranslation}.json` // _fr.translated-page-name.json\n```\n\nThese files can either be directly in the page directory or grouped in a special directory called `i18n` by default.\nConcerning custom groups, create a directory containing the translation files named after your group in `src/i18n`. To make them accessible in a page check the `loadingRules`.\n\nTo get **type-safety and generate your translated routes run the following command `npm run i18n:sync`**.\n\nOnce you have some translations you can use the main translation function `t` :\n\n```ts\n// src/pages/about/_i18n/en.json\n{\n\t\"my_nested\": {\n\t\t\"translation_key\": \"hello\"\n\t}\n}\n// src/pages/about/_i18n/fr.a-propos.json\n{\n\t\"my_nested\": {\n\t\t\"translation_key\": \"bonjour\"\n\t}\n}\n```\n\n```ts\n// src/pages/about/index.astro\nimport { astroI18n, t } from \"astro-i18n\"\n\nastroI18n.locale // \"en\"\nt(\"my_nested.translation_key\") // hello\n\n// src/pages/fr/a-propos/index.astro\nimport { astroI18n, t } from \"astro-i18n\"\n\nastroI18n.locale // \"fr\"\nt(\"my_nested.translation_key\") // bonjour\n```\n\nIf there's duplicate keys `t` will return the more specific one (custom group =\u003e local page =\u003e common group).\n\nTo translate a link use the `l` function, **by default all route segment translations are visible on the client-side** :\n\n```ts\n// src/pages/fr/a-propos/index.astro\nimport { astroI18n, l } from \"astro-i18n\"\n\nastroI18n.locale // \"en\"\nl(\"/about\") // \"about\"\n\n// src/pages/fr/a-propos/index.astro\nimport { astroI18n, l } from \"astro-i18n\"\n\nastroI18n.locale // \"fr\"\nl(\"/about\") // \"a-propos\"\n```\n\nThat's it for the basics, for more keep reading !\n\n## Configuration\n\n### `primaryLocale`\n\nThe default locale for your app.\n\n### `secondaryLocales`\n\nAll the other locales supported by your app.\n\n### `fallbackLocale`\n\nIf left undefined this will be equal to your `primaryLocale`, to disable it set it to an empty string. The locale to search a translation in when it's missing in another one.\n\n### `showPrimaryLocale`\n\nBoolean deciding whether to show the default locale in the url or not.\n\n### `trailingSlash`\n\nPossible values are `\"always\"` or `\"never\"` (default). When set to `\"always\"`, generated routes will have a trailing slash.\n\n### `run`\n\nPossible values are `\"server\"` or `\"client+server\"` (default). When set to `\"client+server\"` the available translations for the current page and all route translations will be serialized and sent to the client so that astro-i18n can work with client-side frameworks. You can disable this behaviour by setting `run` to `\"server\"`.\n\n### `translations`\n\nYour app translations in the following format :\n\n```json\n{\n\t\"common\": {\n\t\t\"en\": {\n\t\t\t\"hello\": \"Hello\",\n\t\t\t\"form\": {\n\t\t\t\t// accessed with the \"form.first_name\" key :\n\t\t\t\t\"first_name\": \"First name\"\n\t\t\t}\n\t\t},\n\t\t\"fr\": {\n\t\t\t\"hello\": \"Bonjour\",\n\t\t\t\"form\": {\n\t\t\t\t\"first_name\": \"Prénom\"\n\t\t\t}\n\t\t}\n\t},\n\t\"admin\": {\n\t\t\"en\": {\n\t\t\t\"how_are_you\": \"How are you ?\"\n\t\t},\n\t\t\"fr\": {\n\t\t\t\"how_are_you\": \"Comment allez vous ?\"\n\t\t}\n\t},\n\t// routes are also a valid group, they will automatically load in the corresponding page :\n\t\"/posts/[id]\": {\n\t\t\"en\": {\n\t\t\t\"bye\": \"Bye\"\n\t\t},\n\t\t\"fr\": {\n\t\t\t\"bye\": \"Au revoir\"\n\t\t}\n\t}\n}\n```\n\n### `translationLoadingRules`\n\nAn array of rules specifying what group to load on which page :\n\n```json\n[\n\t{\n\t\t\"routes\": [\"^/admin.*\"], // regex for all routes beginning with \"/admin\"\n\t\t\"groups\": [\"^admin$\"] // regex to load the \"admin\" group\n\t}\n]\n```\n\n### `translationDirectory`\n\nWhich name to use for the directories in `src` and in `src/pages`, by default it's `\"i18n\"` (`/src/i18n` and `src/pages/i18n`/`src/pages/about/i18n`).\n\n```json\n{\n\t\"i18n\": \"i18n\",\n\t\"pages\": \"i18n\"\n}\n```\n\n### `routes`\n\nYour route segment translations. This is where you can translate your routes, for example `\"/about\"` to `\"/a-propos\"`.\n\n```json\n{\n\t// only specify secondary locales\n\t\"fr\": {\n\t\t// \"primary_locale_segment\": \"translated_segment\"\n\t\t\"about\": \"a-propos\"\n\t}\n}\n```\n\n## Variants\n\nVariants are the way that plurals and context are handled. You can set multiple variables, numbers will match the closest value however other types will only match exact matches. They are set in the translation key, here's some examples :\n\n```json\n{\n\t\"car\": \"There is a car.\", // default value for \"car\" key\n\t\"car{{ cars: 2 }}\": \"There are two cars.\",\n\t\"car{{ cars: 3 }}\": \"There are many cars.\",\n\t\"foo{{ multiple: [true, 'bar'] }}\": \"baz.\",\n\t\"context{{ weather: 'rain' }}\": \"It's rainy.\",\n\t\"context{{ weather: 'sun' }}\": \"It's sunny.\"\n}\n```\n\n```ts\nimport { astroI18n, t } from \"astro-i18n\"\n\nt(\"car\") // \"There is a car.\"\nt(\"car\", { cars: 0 }) // \"There are two cars.\"\nt(\"car\", { cars: 1 }) // \"There are two cars.\"\nt(\"car\", { cars: 2 }) // \"There are two cars.\"\nt(\"car\", { cars: 3 }) // \"There are many cars.\"\nt(\"car\", { cars: 18 }) // \"There are many cars.\"\nt(\"foo\", { multiple: true }) // \"baz.\"\nt(\"foo\", { multiple: \"bar\" }) // \"baz.\"\nt(\"context\", { weather: \"rain\" }) // \"It's rainy.\"\nt(\"context\", { weather: \"sun\" }) // \"It's sunny.\"\n```\n\n## Interpolations\n\nInterpolations can be used to display dynamic data in your translation values, here's some examples :\n\n```json\n{\n\t\"interpolation_1\": \"{# variable #}\",\n\t\"interpolation_2\": \"{# variable(alias) #}\",\n\t\"interpolation_3\": \"{# variable(alias)\u003euppercase #}\",\n\t\"interpolation_4\": \"{# 15\u003eintl_format_number({ style: 'currency', currency: currency }) #}\",\n\t\"interpolation_5\": \"{# { foo: 'bar'}(alias)\u003ejson(false)\u003euppercase #}\"\n}\n```\n\n```ts\nimport { astroI18n, t } from \"astro-i18n\"\n\nastroI18n.locale // \"en\"\n\nt(\"interpolation_1\", { variable: \"foo\" }) // \"foo\"\nt(\"interpolation_2\", { alias: \"bar\" }) // \"bar\"\nt(\"interpolation_3\", { alias: \"baz\" }) // \"BAZ\"\nt(\"interpolation_4\", { currency: \"EUR\" }) // \"€15.00\"\nt(\"interpolation_5\") // '{\"FOO\":\"BAR\"}'\nt(\"interpolation_5\", { alias: { bar: \"baz\" } }) // '{\"BAR\":\"BAZ\"}'\n```\n\nAs you can see there's multiple parts to an interpolation :\n\n-   The interpolation value which is what the formatters apply on.\n-   The interpolation alias which can either change the name of the value if it's a variable or make the value a default value replaceable by the alias variable.\n-   The interpolation formatters which can take arguments, you can chain them and create your own. When creating a formatter, if you want it to work on the client side it must not use anything outside the function scope.\n\nYou can apply this to any value, even if it's nested or inside the formatters arguments.\n\n## Interpolation formatters\n\nAn interpolation formatter is a function that takes the interpolation value and transforms it, if there's multiple of them they will be chained.\nThese are the default interpolation formatters :\n\n-   `upper()` : `value.toUpperCase()`.\n-   `uppercase()` : alias for `upper`.\n-   `lower()` : `value.toLowerCase()`.\n-   `lowercase()` : alias for `lower`.\n-   `capitalize()` : Capitalizes the first character of `value` and lowers the others.\n-   `json(format = true)` : `JSON.stringify(value)`, if format is true it will format the JSON.\n-   `default_falsy(defaultValue)` : When `!value`, `defaultValue` will be used instead.\n-   `default(defaultValue)` : alias for `default_falsy`.\n-   `default_nullish(defaultValue)` : When `value == null`, `defaultValue` will be used instead.\n-   `default_non_string(defaultValue)` : When `typeof value !== \"string\"`, `defaultValue` will be used instead.\n-   `intl_format_number(options, locale = astroI18n.locale)` : Formats `value` with [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).\n-   `intl_format_date(options, locale = astroI18n.locale)` : Formats `value` with [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat).\n\nIf the formatter has no arguments you can write it without parenthesises, for example `value\u003eupper`.\n\nYou can add your own custom formatters in the second argument of the middleware. If you want them to work on the client-side they must not use anything outside the function scope. Here's an example of a custom formatter :\n\n```ts\nexport function xXify(value: unknown, repeats: unknown = 1) {\n\tif (typeof repeats !== \"number\") {\n\t\tthrow new Error(\"repeats must be a number\")\n\t}\n\tif (repeats \u003c= 0) return value\n\treturn `${\"xX\".repeat(repeats)}${value}${\"Xx\".repeat(repeats)}`\n}\n\n// example that wouldn't work client-side :\nconst error = new Error(\"repeats must be a number\")\nfunction xXify(value: unknown, repeats: unknown = 1) {\n\tif (typeof repeats !== \"number\") throw error // cannot use anything outside of scope\n\t// ...\n}\n```\n\n## Reference\n\n### `defineAstroI18nConfig`\n\n```ts\nfunction defineAstroI18nConfig(\n\tconfig: Partial\u003cAstroI18nConfig\u003e,\n): Partial\u003cAstroI18nConfig\u003e\n```\n\nUtility function to get type safety when defining the config.\n\n### `useAstroI18n`\n\n```ts\nfunction useAstroI18n(\n\tconfig?: Partial\u003cAstroI18nConfig\u003e | string, // string if path to config file\n\tformatters?: {\n\t\t[name: string]: (value: unknown, ...args: unknown[]) =\u003e unknown\n\t},\n): AstroMiddleware\n```\n\nThe astro-i18n middleware, mandatory to make the library work.\n\n### `createGetStaticPaths`\n\n```ts\nfunction createGetStaticPaths(\n\tcallback: (\n\t\tprops: GetStaticPathsProps,\n\t) =\u003e GetStaticPathsItem[] | Promise\u003cGetStaticPathsItem[]\u003e,\n): GetStaticPathsItem[] | Promise\u003cGetStaticPathsItem[]\u003e\n```\n\nYou should use this function if you plan to use any translation features inside a getStaticPaths. This is to fix some Astro behaviour. Using the `t` function is not recommended inside getStaticPaths.\n\n### `t`\n\nAlias for `astroI18n.t`.\n\n### `l`\n\nAlias for `astroI18n.l`.\n\n### `astroI18n.environment`\n\n```ts\nenvironment: \"node\" | \"none\" | \"browser\"\n```\n\nThe current detected environment.\n\n### `astroI18n.route`\n\n```ts\nroute: string\n```\n\nThe current route (without the locale, for example `/fr/about` will return `/about`).\n\n### `astroI18n.pages`\n\n```ts\npages: string[]\n```\n\nAn array of all the available pages. At the moment it only detects pages for which you have a translation (even if it's empty).\n\n### `astroI18n.page`\n\n```ts\npage: string\n```\n\nThe corresponding page for the current route, for example `/posts/my-cool-slug` will be `/posts/[slug]`. Only gets detected if you have a translation for that page (even if it's empty).\n\n### `astroI18n.locale`\n\n```ts\nlocale: string\n```\n\nThe locale for the current page.\n\n### `astroI18n.locales`\n\n```ts\nlocales: string[]\n```\n\nAll the supported locales.\n\n### `astroI18n.primaryLocale`\n\n```ts\nprimaryLocale: string\n```\n\nThe configured primary locale.\n\n### `astroI18n.secondaryLocales`\n\n```ts\nsecondaryLocales: string\n```\n\nThe configured secondary locales.\n\n### `astroI18n.fallbackLocale`\n\n```ts\nfallbackLocale: string\n```\n\nThe configured fallback locale.\n\n### `astroI18n.isInitialized`\n\n```ts\nisInitialized: boolean\n```\n\nTrue once the config has been loaded and the state initialized.\n\n### `astroI18n.t`\n\n```ts\nfunction t(\n\tkey: string,\n\tproperties?: Record\u003cstring, unknown\u003e,\n\toptions?: {\n\t\troute?: string\n\t\tlocale?: string\n\t\tfallbackLocale?: string\n\t},\n): string\n```\n\nThe main translation function (`t` is an alias).\n\n### `astroI18n.l`\n\n```ts\nfunction l(\n\troute: string,\n\tparameters?: Record\u003cstring, unknown\u003e,\n\toptions?: {\n\t\ttargetLocale?: string\n\t\trouteLocale?: string\n\t\tshowPrimaryLocale?: boolean\n\t\tquery?: Record\u003cstring, unknown\u003e\n\t},\n): string\n```\n\nThe main routing function (`l` is an alias).\n\n### `astroI18n.addTranslations`\n\n```ts\nfunction addTranslations(translations: {\n\t[group: string]: {\n\t\t[locale: string]: DeepStringRecord\n\t}\n}): AstroI18n\n```\n\nAdds new translations at runtime.\n\n### `astroI18n.addFormatters`\n\n```ts\nfunction addFormatters(formatters: {\n\t[name: string]: (value: unknown, ...args: unknown[]) =\u003e unknown\n}): AstroI18n\n```\n\nAdds new formatters at runtime.\n\n### `astroI18n.addTranslationLoadingRules`\n\n```ts\nfunction addTranslationLoadingRules(\n\ttranslationLoadingRules: {\n\t\tgroups: string[]\n\t\troutes: string[]\n\t}[],\n): AstroI18n\n```\n\nAdds new translation loading rules at runtime.\n\n### `astroI18n.addRoutes`\n\n```ts\nfunction addRoutes(routes: {\n\t[secondaryLocale: string]: {\n\t\t[segment: string]: string\n\t}\n}): AstroI18n\n```\n\nAdds new route segment translations at runtime.\n\n### `astroI18n.extractRouteLocale`\n\n```ts\nfunction extractRouteLocale(route: string): string\n```\n\nUtility function to parse one of the configured locales out of the given route.\n\n### `astroI18n.redirect`\n\n```ts\nfunction redirect(destination: string | URL, status = 301): void\n```\n\nUse this instead of `Astro.redirect` to redirect users.\n\n## Components\n\nYou can use these premade components in your project :\n\n### HrefLangs\n\nSEO component, see [hreflang](https://en.wikipedia.org/wiki/Hreflang).\n\n```astro\n---\nimport { astroI18n } from \"astro-i18n\"\n\nconst params: Record\u003cstring, string\u003e = {}\nfor (const [key, value] of Object.entries(Astro.params)) {\n\tif (value === undefined) continue\n\tparams[key] = String(value)\n}\n\nconst hrefLangs = astroI18n.locales.map((locale) =\u003e ({\n\thref:\n\t\tAstro.url.origin +\n\t\tastroI18n.l(Astro.url.pathname, params, {\n\t\t\ttargetLocale: locale,\n\t\t}),\n\threflang: locale,\n}))\n---\n\n{\n\threfLangs.map(({ href, hreflang }) =\u003e (\n\t\t\u003clink rel=\"alternate\" href={href} hreflang={hreflang} /\u003e\n\t))\n}\n\n```\n\n### LocaleSwitcher\n\nSimple component to switch locales.\n\n```astro\n---\nimport { astroI18n, l } from \"astro-i18n\"\n\ninterface Props {\n\tshowCurrent: boolean\n\tlabels: {\n\t\t[locale: string]: string\n\t}\n}\n\nconst { showCurrent = true, labels = {} } = Astro.props\n\nconst params: Record\u003cstring, string\u003e = {}\nfor (const [key, value] of Object.entries(Astro.params)) {\n\tif (value === undefined) continue\n\tparams[key] = String(value)\n}\n\nlet links = astroI18n.locales.map((locale) =\u003e ({\n\tlocale,\n\thref: l(Astro.url.pathname, params, {\n\t\ttargetLocale: locale,\n\t}),\n\tlabel: labels[locale] || locale.toUpperCase(),\n}))\n\nif (!showCurrent) {\n\tlinks = links.filter((link) =\u003e link.locale !== astroI18n.locale)\n}\n---\n\n\u003cnav\u003e\n\t\u003cul\u003e\n\t\t{\n\t\t\tlinks.map(({ href, label }) =\u003e (\n\t\t\t\t\u003cli\u003e\n\t\t\t\t\t\u003ca href={href}\u003e{label}\u003c/a\u003e\n\t\t\t\t\u003c/li\u003e\n\t\t\t))\n\t\t}\n\t\u003c/ul\u003e\n\u003c/nav\u003e\n\n```\n\n## CLI\n\n### `astro-i18n install`\n\nGenerates default files and add i18n commands to `package.json`.\n\n### `astro-i18n generate:pages`\n\nGenerates the pages corresponding to your current config and `src/pages` folder. You can use the `--purge` argument to clear old pages.\n\n### `astro-i18n generate:types`\n\nGenerates types in `src/env.d.ts` to give you type safety according to your config \u0026 translations.\n\n### `astro-i18n extract:keys`\n\nExtract all the translation keys used in `src/pages` (as long as they are strings and not variables), and adds them to your i18n folder for every locale.\nThis is useful if you use your keys as values.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandre-fernandez%2Fastro-i18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexandre-fernandez%2Fastro-i18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandre-fernandez%2Fastro-i18n/lists"}