mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f4494527 | ||
|
|
fe75e7ca94 | ||
|
|
a9fdc595f5 | ||
|
|
eee372b21e | ||
|
|
220a9cbf00 | ||
|
|
81c23b0b8d | ||
|
|
2b5a59b33d | ||
|
|
d8dabcf62e | ||
|
|
02281aa6c7 | ||
|
|
ef16a887d2 | ||
|
|
880cb1972d | ||
|
|
979f2bdedd | ||
|
|
7cb833df1e | ||
|
|
682133f893 | ||
|
|
9bff4db4c1 | ||
|
|
70c4dfab73 | ||
|
|
e1c501caad | ||
|
|
b883a37aeb | ||
|
|
951f36381f | ||
|
|
ae71089e74 | ||
|
|
e45e0fdde9 | ||
|
|
db449d439a |
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
@@ -12,15 +12,17 @@ Represents the VIntl module options.
|
||||
|
||||
**Properties**
|
||||
|
||||
| Property | Default | Description |
|
||||
| :---------------------- | :------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `defaultLocale` | | A string that represents the BCP 47 code of the locale to use by default. |
|
||||
| `resolveDir` | `'.'` | An optional string that represents a directory where all the imports associated with the locales are being resolved. If it's relative, then it will be resolved against the Nuxt `srcDir` directory. If this property is not set, modules will be resolved from the Nuxt `srcDir`. |
|
||||
| `locales` | | An array of [`LocaleDescriptor`] objects that represent all the configured locales. |
|
||||
| `storage` | | An optional string that represents either a built-in storage name (`localStorage` / `cookie`), or a node import specifier that exports a custom storage adapter as the default export. |
|
||||
| `broadcastLocaleChange` | `true` | An optional boolean value that determines whether to use [`BroadcastChannel`] (if available) to synchronize the locale changes between the tabs on the same origin. |
|
||||
| `hostLanguageParam` | `'hl'` | An optional name of the query parameter used to override the user language (including automatically detected one). It is used to generate SEO meta tags. Using `null` will disable them. |
|
||||
| `parserless` | `{ enabled: false }` | An optional [`ParserlessOptions`] object or [`ParserlessMode`] which configures the parserless mode. |
|
||||
| Property | Default | Description |
|
||||
| :---------------------- | :-----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `defaultLocale` | | A string that represents the BCP 47 code of the locale to use by default. |
|
||||
| `resolveDir` | `'.'` | An optional string that represents a directory where all the imports associated with the locales are being resolved. If it's relative, then it will be resolved against the Nuxt `srcDir` directory. If this property is not set, modules will be resolved from the Nuxt `srcDir`. |
|
||||
| `locales` | | An array of [`LocaleDescriptor`] objects that represent all the configured locales. |
|
||||
| `storage` | | An optional string that represents either a built-in storage name (`localStorage` / `cookie`), or a node import specifier that exports a custom storage adapter as the default export. |
|
||||
| `broadcastLocaleChange` | `true` | An optional boolean value that determines whether to use [`BroadcastChannel`] (if available) to synchronize the locale changes between the tabs on the same origin. |
|
||||
| `hostLanguageParam` | `'hl'` | **Deprecated.** An optional name of the query parameter used to override the user language (including automatically detected one). It is used to generate SEO meta tags. Using `null` will disable them. |
|
||||
| `parserless` | `<...>` | An optional [`ParserlessOptions`] object or [`ParserlessMode`] which configures the parserless mode. |
|
||||
| `seo` | `<...>` | An optional [`SEOOptions`] object which configures automatic head tags to improve discoverability of other languages by search engines. |
|
||||
| `onParseError` | | An optional [`ParsingErrorHandlingOption`] value that defines how to handle errors that occur when transforming message files. If not defined, the errors are not handled and thrown during the building, preventing it from succeeding. |
|
||||
|
||||
[`BroadcastChannel`]: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
|
||||
[`ParserlessOptions`]: #parserlessoptions
|
||||
@@ -131,9 +133,9 @@ Represents an import source for the messages file. It is similar to the [`Import
|
||||
|
||||
Represents options for the parserless mode.
|
||||
|
||||
| Property | Description |
|
||||
| :-------- | :----------------------------------------------------------------------- |
|
||||
| `enabled` | A `ParserlessMode` value representing the status of the parserless mode. |
|
||||
| Property | Default | Description |
|
||||
| :-------- | :-----: | :----------------------------------------------------------------------- |
|
||||
| `enabled` | `false` | A `ParserlessMode` value representing the status of the parserless mode. |
|
||||
|
||||
## `ParserlessMode`
|
||||
|
||||
@@ -144,3 +146,39 @@ A string or boolean value representing the status of the parserless mode.
|
||||
| `always` | `true` | Parserless mode is always enabled, regardless of the mode in which Nuxt is running. |
|
||||
| `only-prod` | | Parserless mode is only enabled when building Nuxt app for production. |
|
||||
| `never` | `false` | Parserless mode is never enabled, the parser is always present in the runtime. |
|
||||
|
||||
## `SEOOptions`
|
||||
|
||||
[`SEOOptions`]: #seooptions
|
||||
|
||||
Represents options for the automatic head tags.
|
||||
|
||||
By default VIntl will add `<link>` tags for every language to make search engines aware of other languages, you can disable this by setting `enabled` to `false`.
|
||||
|
||||
| Property | Default | Description |
|
||||
| :-------------------------- | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `enabled` | `true` | A boolean value representing whether VIntl should automatically add `<link>` tags to `<head>` of the site. These tags make search engines aware that your site is provided in different languages. |
|
||||
| `hostLanguageParameter`: | `hl` | A string value representing the query parameter that VIntl would use to configure the language of the site. |
|
||||
| `baseURL` | | **Recommended.** An optional string value representing the base URL of the site. If not set, VIntl will try to guess the URL based on inital request URL or the current `window.location`. |
|
||||
| `defaultLocaleHasParameter` | `true` | A boolean value representing whether the default locale should have a host language parameter. Disabling this may make URLs of visitors cleaner, however it is not correct, because lack of host language instructs VIntl to use user's language (either the one they've used before or browser's one). |
|
||||
| `xDefaultHreflang` | `true` | A boolean value representing whether the `x-default` hreflang is present. |
|
||||
|
||||
## `ParsingErrorHandlingOption`
|
||||
|
||||
[`ParsingErrorHandlingOption`]: #parsingerrorhandlingoption
|
||||
|
||||
Represents a value for the message pasing error handling option.
|
||||
|
||||
It can be one of the following:
|
||||
|
||||
- A string containing a name of the strategy for handling the errors. The following strategies are available:
|
||||
|
||||
| Name | Strategy |
|
||||
| :----------------------- | :-------------------------------------------------------------------------------- |
|
||||
| `use-message-as-literal` | Use the raw message contents as a literal. |
|
||||
| `use-id-as-literal` | Use the message ID as a literal. |
|
||||
| `use-empty-literal` | Use empty string as a literal. |
|
||||
| `log-and-skip` | Log the error to console and don't include the message into the transformed file. |
|
||||
| `skip` | Skip including the message into the transformed file. |
|
||||
|
||||
- An function that takes in a context for the error (that includes error itself) and optionally returns a fallback AST (not returning anything skips including the message into the transformed file).
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"start": "node .output/server/index.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^6.1.2",
|
||||
"@nuxt-themes/docus": "^1.12.3",
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"@formatjs/cli": "^6.2.0",
|
||||
"@nuxt-themes/docus": "^1.15.0",
|
||||
"@nuxt/eslint-config": "^0.2.0",
|
||||
"@vintl-dev/eslint-config-peony": "workspace:^",
|
||||
"@vintl/nuxt": "workspace:^",
|
||||
"eslint": "^8.42.0",
|
||||
"nuxi": "^3.5.3",
|
||||
"nuxt": "^3.5.3",
|
||||
"eslint": "^8.51.0",
|
||||
"nuxi": "^3.9.0",
|
||||
"nuxt": "^3.7.4",
|
||||
"theme-colors": "^0.0.5",
|
||||
"vue": "^3.3.4"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<users>
|
||||
<user>9491DAF6E17F64A987F923EC70F0BAEE</user>
|
||||
</users>
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"broken-message": {
|
||||
"message": "This is {broken, }"
|
||||
},
|
||||
"example": {
|
||||
"message": "Example"
|
||||
},
|
||||
@@ -8,6 +11,9 @@
|
||||
"index.other-page": {
|
||||
"message": "To the other page"
|
||||
},
|
||||
"index.use-automatic": {
|
||||
"message": "Use automatic"
|
||||
},
|
||||
"language": {
|
||||
"message": "Language"
|
||||
},
|
||||
@@ -17,6 +23,9 @@
|
||||
"other-page.back": {
|
||||
"message": "Go back"
|
||||
},
|
||||
"other-page.description": {
|
||||
"message": "You must've clicked me a thousand times"
|
||||
},
|
||||
"other-page.title": {
|
||||
"message": "Hello from the other page!"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ export default defineNuxtConfig({
|
||||
format: 'crowdin',
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
static: {
|
||||
iso: 'en',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'uk',
|
||||
@@ -30,5 +35,10 @@ export default defineNuxtConfig({
|
||||
storage: 'cookie',
|
||||
broadcastLocaleChange: true,
|
||||
parserless: 'always',
|
||||
seo: {
|
||||
xDefaultHreflang: false,
|
||||
defaultLocaleHasParameter: false,
|
||||
},
|
||||
onParseError: 'log-and-skip',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
"generate": "nuxi generate",
|
||||
"preview": "nuxi preview",
|
||||
"lint": "eslint --cache .",
|
||||
"intl:extract": "formatjs extract \"{,components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file locales/en-US.json --format simple",
|
||||
"intl:extract": "formatjs extract \"{,components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file locales/en-US.json --format crowdin",
|
||||
"start": "node .output/server/index.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vintl-dev/eslint-config-peony": "workspace:^",
|
||||
"@vintl-dev/pg-messages": "workspace:^",
|
||||
"@vintl/nuxt": "workspace:^",
|
||||
"eslint": "^8.42.0",
|
||||
"nuxi": "^3.5.3",
|
||||
"nuxt": "^3.5.3"
|
||||
"eslint": "^8.51.0",
|
||||
"nuxi": "^3.9.0",
|
||||
"nuxt": "^3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,47 +23,84 @@ const messages = defineMessages({
|
||||
id: 'index.other-page',
|
||||
defaultMessage: 'To the other page',
|
||||
},
|
||||
useAutomatic: {
|
||||
id: 'index.use-automatic',
|
||||
defaultMessage: 'Use automatic',
|
||||
},
|
||||
brokenMessage: {
|
||||
id: 'broken-message',
|
||||
defaultMessage: 'This is {broken, }'
|
||||
},
|
||||
} as const)
|
||||
|
||||
const vintl = useVIntl()
|
||||
const { formatMessage } = vintl
|
||||
|
||||
const changing = ref(false)
|
||||
|
||||
async function changeLocale(locale: string) {
|
||||
if (changing.value) return
|
||||
changing.value = true
|
||||
try {
|
||||
await vintl.changeLocale(locale)
|
||||
} catch (err) {
|
||||
console.error('Error changing locale', err)
|
||||
} finally {
|
||||
changing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
const currentLocale = computed({
|
||||
get() {
|
||||
return vintl.locale
|
||||
},
|
||||
set() {
|
||||
// ignore
|
||||
set(value) {
|
||||
changeLocale(value).then(noop, noop)
|
||||
},
|
||||
})
|
||||
|
||||
async function onLocaleChange(e: Event) {
|
||||
await vintl.changeLocale(
|
||||
(e as Event & { target: HTMLSelectElement }).target.value,
|
||||
)
|
||||
}
|
||||
const useAutomatic = computed({
|
||||
get() {
|
||||
return vintl.automatic
|
||||
},
|
||||
set(value) {
|
||||
changeLocale(value ? 'auto' : vintl.locale).then(noop, noop)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<h3>{{ formatMessage(messages.options) }}</h3>
|
||||
<label for="language-select">
|
||||
{{ formatMessage(messages.language) }}
|
||||
</label>
|
||||
<select
|
||||
id="language-select"
|
||||
v-model="currentLocale"
|
||||
@change="onLocaleChange"
|
||||
>
|
||||
<option
|
||||
v-for="locale in [...$nuxt.$i18n.$locales.value.keys()]"
|
||||
:key="locale.tag"
|
||||
:value="locale.tag"
|
||||
>
|
||||
{{ locale.tag }}
|
||||
</option>
|
||||
</select>
|
||||
<div>
|
||||
<label>
|
||||
<input v-model="useAutomatic" type="checkbox" />
|
||||
{{ formatMessage(messages.useAutomatic) }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
{{ formatMessage(messages.language) }}
|
||||
<select
|
||||
id="language-select"
|
||||
v-model="currentLocale"
|
||||
:disabled="vintl.automatic"
|
||||
@change="onLocaleChange"
|
||||
>
|
||||
<option
|
||||
v-for="locale in [...$nuxt.$i18n.$locales.value.keys()]"
|
||||
:key="locale.tag"
|
||||
:value="locale.tag"
|
||||
>
|
||||
{{ locale.tag }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.1",
|
||||
"turbo": "^1.10.1"
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"turbo": "^1.10.15"
|
||||
},
|
||||
"scripts": {
|
||||
"pg:prepare": "turbo run --no-daemon @vintl-dev/pg#dev:prepare",
|
||||
@@ -36,5 +36,5 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.6.1"
|
||||
"packageManager": "pnpm@8.8.0"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
"no-useless-constructor": "off",
|
||||
"@typescript-eslint/no-useless-constructor": "warn",
|
||||
"brace-style": "off",
|
||||
"vue/singleline-html-element-content-newline": "off"
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/html-self-closing": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"extraFileExtensions": [".vue"]
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"config.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
||||
"@typescript-eslint/parser": "^5.59.9",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"typescript": "^5.1.3"
|
||||
"@rushstack/eslint-patch": "^1.5.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "^8.40.0",
|
||||
"eslint": "^8.42.0"
|
||||
"@types/eslint": "^8.44.3",
|
||||
"eslint": "^8.51.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,77 @@
|
||||
# @vintl/nuxt
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a9fdc59: Add support for parsing the messages in the error handlers
|
||||
|
||||
With [update to `@vintl/unplugin`](https://github.com/vintl-dev/unplugin/releases/tag/v1.5.0), it's now possible to use `parseMessage` from the error handler's context to parse custom messages or re-parse the current message with any modified options.
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 81c23b0: Support for parsing error handling
|
||||
|
||||
`@vintl/nuxt` can now handle errors when parsing message files containing broken messages in a configurable manner.
|
||||
|
||||
You can configure how errors are handled using `onParseError` module option, which can either be a name of the built-in strategy, or a function that takes in context and optionally provides fallback message AST.
|
||||
|
||||
Check out documentation for more information.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2b5a59b: Unpin FormatJS dependencies
|
||||
|
||||
We proveviously had `@formatjs/intl` pinned because of the broken ESM migration, however this has been fixed for quite some time already, so the pinned version has been to changed to a range, which ensures that `@formatjs/intl` can be updated with latest bug fixes and improvements until the next major version.
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 979f2bd: Add more SEO options
|
||||
|
||||
Two options have been added to the SEO configuration:
|
||||
|
||||
- `defaultLocaleHasParameter` allows to disable the host language parameter for the default locale.
|
||||
|
||||
This is not recommended and not correct, because lack of the host language parameter instructs VIntl to use user's language (either the one they've used before or browser's one). However, it may be useful in some cases.
|
||||
|
||||
- `xDefaultHreflang` allows to disable the `x-default` hreflang in case it's not useful to have (e.g. you disable the automatic language detection), although this is quite rare.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- e1c501c: Improve alternate links generation
|
||||
|
||||
Generation of `alternate` `hreflang` links has been improved to properly use absolute URLs.
|
||||
|
||||
Since there's no good way to know the URL of the site, there's now also `seo.baseURL` option which allows to set site's proper domain.
|
||||
|
||||
`hostLanguageParam` option has been deperecated in favour of `seo.hostLanguageParameter`, which functions almost the same way, except it does not accept `null`. If you want to disable generation of such tags or handle them yourself, you can set `seo.enabled` to `false`.
|
||||
|
||||
See [Configuration](https://vintl-nuxt.netlify.app/introduction/configuration#seooptions) documentation page for more details.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 951f363: Add graceful fallback for unknown locales in storage
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ae71089: Use automatic locale if none returned by the storage
|
||||
|
||||
Fixed a bug where automatic locale would not be restored from the storage properly on the next page reload, despite it being saved properly.
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- db449d4: Fix missing options in the newer Nuxt versions
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vintl/nuxt",
|
||||
"version": "1.2.2",
|
||||
"version": "1.7.0",
|
||||
"description": "Nuxt Module for VIntl integration",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
@@ -34,33 +34,36 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "nuxt-module-build",
|
||||
"build": "nuxt-module-build build",
|
||||
"lint": "eslint --cache .",
|
||||
"prepack": "pnpm run -s lint && pnpm run -s build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/module-builder": "^0.4.0",
|
||||
"@nuxt/schema": "^3.5.3",
|
||||
"@nuxt/module-builder": "^0.5.2",
|
||||
"@nuxt/schema": "^3.7.4",
|
||||
"@types/hash-sum": "^1.0.0",
|
||||
"@types/node": "^18.16.16",
|
||||
"@unhead/vue": "^1.1.27",
|
||||
"@types/node": "^18.18.4",
|
||||
"@unhead/vue": "^1.7.4",
|
||||
"@vintl-dev/eslint-config-peony": "workspace:^",
|
||||
"hookable": "^5.5.3",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"typescript": "^5.1.3"
|
||||
"prettier-plugin-jsdoc": "^1.1.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl": "2.7.2",
|
||||
"@nuxt/kit": "^3.5.3",
|
||||
"@vintl/unplugin": "^1.2.4",
|
||||
"@vintl/vintl": "^4.2.1",
|
||||
"@formatjs/intl": "^2.9.3",
|
||||
"@formatjs/intl-localematcher": "^0.4.2",
|
||||
"@nuxt/kit": "^3.7.4",
|
||||
"@vintl/unplugin": "^1.5.0",
|
||||
"@vintl/vintl": "^4.2.3",
|
||||
"astring": "^1.8.6",
|
||||
"consola": "^3.1.0",
|
||||
"consola": "^3.2.3",
|
||||
"hash-sum": "^2.0.0",
|
||||
"import-meta-resolve": "^3.0.0",
|
||||
"pathe": "^1.1.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"slash": "^5.1.0",
|
||||
"ufo": "^1.3.1",
|
||||
"zod": "^3.21.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ const optionsFilePath = 'i18n/options.mjs'
|
||||
|
||||
type _InputModuleOptions = typeof moduleOptionsSchema
|
||||
|
||||
export interface InputModuleOptions extends t.input<_InputModuleOptions> {}
|
||||
export interface ModuleOptions extends t.input<_InputModuleOptions> {}
|
||||
|
||||
export default defineNuxtModule<InputModuleOptions>({
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
name: '@vintl/nuxt',
|
||||
configKey: 'vintl',
|
||||
@@ -124,11 +124,31 @@ export default defineNuxtModule<InputModuleOptions>({
|
||||
|
||||
const { icuMessages } = await import('@vintl/unplugin')
|
||||
|
||||
const onParseError = (() => {
|
||||
let { onParseError } = options
|
||||
|
||||
if (onParseError === 'log-and-skip') {
|
||||
onParseError = (ctx) => {
|
||||
consola.warn(
|
||||
`[vintl] Cannot parse the message "${
|
||||
ctx.messageId
|
||||
}" in "${relativizePath(
|
||||
nuxt.options.srcDir,
|
||||
ctx.moduleId,
|
||||
)}". It will me skipped.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return onParseError
|
||||
})()
|
||||
|
||||
extendWebpackConfig((cfg) => {
|
||||
const plugins = (cfg.plugins ??= [])
|
||||
plugins.push(
|
||||
...pluginOptionsBank
|
||||
.createOptions({
|
||||
onParseError,
|
||||
output: {
|
||||
type: parserlessModeEnabled ? 'ast' : 'raw',
|
||||
},
|
||||
@@ -143,6 +163,7 @@ export default defineNuxtModule<InputModuleOptions>({
|
||||
...pluginOptionsBank
|
||||
.createOptions({
|
||||
pluginsWrapping: true,
|
||||
onParseError,
|
||||
output: {
|
||||
type: parserlessModeEnabled ? 'ast' : 'raw',
|
||||
},
|
||||
|
||||
@@ -1020,8 +1020,8 @@ export function generate(
|
||||
new ExportNamedDeclaration().setDeclaration(
|
||||
new VariableDeclaration('const', [
|
||||
new VariableDeclarator(
|
||||
new Identifier('hostLanguageParam'),
|
||||
new Literal(opts.hostLanguageParam),
|
||||
new Identifier('seo'),
|
||||
new Literal(null).setRaw(JSON.stringify(opts.seo)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useHead } from '#imports'
|
||||
import type { SEOOptions } from '@vintl/nuxt-runtime/options'
|
||||
import type { IntlController } from '@vintl/vintl/controller'
|
||||
import { useRouter as _useRouter, useRequestURL } from 'nuxt/app'
|
||||
import { joinURL } from 'ufo'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'nuxt/app'
|
||||
import type { LocationQuery, useRouter as _useRouter$type } from 'vue-router'
|
||||
|
||||
type LocationQuery = ReturnType<
|
||||
typeof useRouter
|
||||
>['currentRoute']['value']['query']
|
||||
const useRouter = _useRouter as typeof _useRouter$type
|
||||
|
||||
// I wish there was a better way to do this T_T
|
||||
function getSeachParams(query: LocationQuery) {
|
||||
@@ -35,11 +36,29 @@ type LangHrefLink = {
|
||||
href: string
|
||||
}
|
||||
|
||||
export function initHead(controller: IntlController<any>, hlParam: string) {
|
||||
type HeadOptions = Omit<SEOOptions, 'enabled'>
|
||||
|
||||
export function initHead(
|
||||
controller: IntlController<any>,
|
||||
options: HeadOptions,
|
||||
) {
|
||||
const router = useRouter()
|
||||
|
||||
const currentRoute = computed(() => router.currentRoute.value)
|
||||
|
||||
const requestURL = useRequestURL()
|
||||
|
||||
const normalizeURL = (url: string) => {
|
||||
try {
|
||||
return options.baseURL == null
|
||||
? new URL(url, requestURL).toString()
|
||||
: joinURL(options.baseURL, url)
|
||||
} catch (err) {
|
||||
console.error(`[vintl] cannot normalize url: ${String(err)}`)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang: () => controller.$config.locale,
|
||||
@@ -51,37 +70,43 @@ export function initHead(controller: IntlController<any>, hlParam: string) {
|
||||
const query = String(getSeachParams(route.query))
|
||||
const { path } = route
|
||||
|
||||
let defaultEntry: LangHrefLink
|
||||
const hrefLangs: Map<string, LangHrefLink> = new Map()
|
||||
|
||||
{
|
||||
if (options.xDefaultHreflang) {
|
||||
const sp = new URLSearchParams(query)
|
||||
sp.delete(hlParam)
|
||||
defaultEntry = {
|
||||
sp.delete(options.hostLanguageParameter)
|
||||
hrefLangs.set('x-default', {
|
||||
// key: `hreflang-default`,
|
||||
rel: 'alternate',
|
||||
hreflang: 'x-default',
|
||||
href: withQueryParams(path, sp.toString()),
|
||||
}
|
||||
href: normalizeURL(withQueryParams(path, sp.toString())),
|
||||
})
|
||||
}
|
||||
|
||||
const hrefLangs: Map<string, LangHrefLink> = new Map()
|
||||
for (const locale of controller.availableLocales) {
|
||||
const hrefLang = locale.meta?.static?.iso ?? locale.tag
|
||||
|
||||
if (hrefLangs.has(hrefLang)) continue
|
||||
|
||||
const sp = new URLSearchParams(query)
|
||||
sp.set(hlParam, locale.tag)
|
||||
if (
|
||||
controller.defaultLocale === locale.tag &&
|
||||
!options.defaultLocaleHasParameter
|
||||
) {
|
||||
sp.delete(options.hostLanguageParameter)
|
||||
} else {
|
||||
sp.set(options.hostLanguageParameter, locale.tag)
|
||||
}
|
||||
|
||||
hrefLangs.set(hrefLang, {
|
||||
// key: `hreflang-${locale.tag}`,
|
||||
rel: 'alternate',
|
||||
hreflang: hrefLang,
|
||||
href: withQueryParams(path, sp.toString()),
|
||||
href: normalizeURL(withQueryParams(path, sp.toString())),
|
||||
})
|
||||
}
|
||||
|
||||
return [defaultEntry, ...hrefLangs.values()]
|
||||
return Array.from(hrefLangs.values())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
localeDefinitions,
|
||||
storageAdapterFactory,
|
||||
broadcastLocaleChange,
|
||||
hostLanguageParam,
|
||||
parserless,
|
||||
seo as seoOptions,
|
||||
} from '@vintl/nuxt-runtime/options'
|
||||
import type { LocaleDescriptor, MessageValueType } from '@vintl/vintl'
|
||||
import type { IntlController } from '@vintl/vintl/controller'
|
||||
@@ -21,6 +21,7 @@ import { useAcceptLanguageHeader } from '@vintl/vintl/sources/header'
|
||||
import { defineNuxtPlugin } from 'nuxt/app'
|
||||
import { syncCaller } from './utils/hookable.js'
|
||||
import { initHead } from './head.js'
|
||||
import { match as matchLocales } from '@formatjs/intl-localematcher'
|
||||
|
||||
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
const locales: LocaleDescriptor[] = Object.entries(localeDefinitions).map(
|
||||
@@ -41,8 +42,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (hostLanguageParam != null) {
|
||||
let hlLocale = nuxtApp._route.query[hostLanguageParam]
|
||||
if (seoOptions.enabled) {
|
||||
let hlLocale = nuxtApp._route.query[seoOptions.hostLanguageParameter]
|
||||
|
||||
if (Array.isArray(hlLocale)) {
|
||||
hlLocale = hlLocale[0]
|
||||
@@ -53,12 +54,23 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (locale != null) {
|
||||
const match = matchLocales(
|
||||
[locale],
|
||||
locales.map(({ tag }) => tag),
|
||||
'en-x-placeholder',
|
||||
)
|
||||
|
||||
locale = match == 'en-x-placeholder' ? undefined : match
|
||||
}
|
||||
|
||||
const plugin = createPlugin<MessageValueType>({
|
||||
injectInto: [nuxtApp],
|
||||
controllerOpts: {
|
||||
defaultLocale,
|
||||
locales,
|
||||
locale,
|
||||
usePreferredLocale: locale == null,
|
||||
listen: {
|
||||
error(event) {
|
||||
nuxtApp.hooks.callHookWith(syncCaller, 'i18n:error', {
|
||||
@@ -139,8 +151,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
setupBroadcasting(controller)
|
||||
}
|
||||
|
||||
if (hostLanguageParam != null) {
|
||||
nuxtApp.hook('vue:setup', () => initHead(controller, hostLanguageParam!))
|
||||
if (seoOptions.enabled) {
|
||||
nuxtApp.hook('vue:setup', () => initHead(controller, seoOptions))
|
||||
}
|
||||
|
||||
await nuxtApp.callHook('i18n:ready', controller)
|
||||
|
||||
@@ -2,6 +2,28 @@ import { z as t } from 'zod'
|
||||
import { tSwitch } from '../utils/zod-utils.js'
|
||||
import { languageTagSchema } from './language-tag.js'
|
||||
import { localeDescriptorSchema } from './locales.js'
|
||||
import { seoOptions } from './seo.js'
|
||||
import type { ParseErrorHandler } from '@vintl/unplugin'
|
||||
|
||||
const parseErrorHandingStrategy = t.union([
|
||||
t.literal('use-message-as-literal'),
|
||||
t.literal('use-id-as-literal'),
|
||||
t.literal('use-empty-literal'),
|
||||
t.literal('log-and-skip'),
|
||||
t.literal('skip'),
|
||||
])
|
||||
|
||||
const parseErrorHandler = t.custom<ParseErrorHandler>(
|
||||
(value) => typeof value === 'function',
|
||||
)
|
||||
|
||||
export const parseErrorHandling = tSwitch((input) => {
|
||||
if (typeof input === 'function') {
|
||||
return parseErrorHandler
|
||||
} else if (typeof input === 'string') {
|
||||
return parseErrorHandingStrategy
|
||||
}
|
||||
})
|
||||
|
||||
const parselessModeEnumSchema = t.enum(['always', 'only-prod', 'never'])
|
||||
|
||||
@@ -167,6 +189,7 @@ export const moduleOptionsSchema = t
|
||||
* user language. `null` to disable. If this value is `null`, the SEO tags
|
||||
* generation will be disabled.
|
||||
*
|
||||
* @deprecated Use `seo` options instead.
|
||||
* @default 'hl' // For example, ?hl=en to use English locale (if defined).
|
||||
*/
|
||||
hostLanguageParam: t
|
||||
@@ -174,8 +197,10 @@ export const moduleOptionsSchema = t
|
||||
.describe(
|
||||
'Name of the host language URL parameter, used to override the default or user language.',
|
||||
)
|
||||
.nullable()
|
||||
.default('hl'),
|
||||
.optional()
|
||||
.nullable(),
|
||||
|
||||
seo: seoOptions.optional(),
|
||||
|
||||
/**
|
||||
* Configuration options for the parserless mode, a mode in which the parser
|
||||
@@ -196,6 +221,29 @@ export const moduleOptionsSchema = t
|
||||
return parselessOptionsSchema
|
||||
}
|
||||
}).default('only-prod'),
|
||||
|
||||
/**
|
||||
* Either a literal name of the strategy or a function that handles the
|
||||
* errors that occur during the parsing of one of the imported files.
|
||||
*
|
||||
* @see https://github.com/vintl-dev/unplugin/blob/df9b3be9c77f78eaf5fe7f8d6fde1655f2bc79e1/README.md#onparseerror
|
||||
*/
|
||||
onParseError: parseErrorHandling.optional(),
|
||||
})
|
||||
.transform((from) => {
|
||||
if (from.seo === undefined) {
|
||||
let hostLanguageParam = from.hostLanguageParam
|
||||
if (hostLanguageParam === undefined) {
|
||||
hostLanguageParam = 'hl'
|
||||
}
|
||||
|
||||
from.seo = seoOptions.parse({
|
||||
enabled: hostLanguageParam != null,
|
||||
hostLanguageParameter: hostLanguageParam ?? undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return from as typeof from & { seo: t.output<typeof seoOptions> }
|
||||
})
|
||||
.refine((value) => {
|
||||
return value.locales.some((locale) => locale.tag === value.defaultLocale)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { z as t } from 'zod'
|
||||
|
||||
export const seoOptions = t.object({
|
||||
/** Whether CEO features are enabled. */
|
||||
enabled: t.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Name of the host language URL parameter, used to override the default or
|
||||
* user language.
|
||||
*
|
||||
* @default 'hl' // For example, ?hl=en to use English locale (if defined).
|
||||
*/
|
||||
hostLanguageParameter: t
|
||||
.string()
|
||||
.describe(
|
||||
'Name of the host language URL parameter, used to override the default or user language.',
|
||||
)
|
||||
.optional()
|
||||
.default('hl'),
|
||||
|
||||
/**
|
||||
* Base URL at which the site will be available. This is required to generate
|
||||
* absolute hreflang links. Without this, VIntl will try to guess the URL
|
||||
* based on the server request URL / current `window.location`.
|
||||
*/
|
||||
baseURL: t
|
||||
.string()
|
||||
.describe(
|
||||
'Base URL at which the site will be available. This is required to generate absolute hreflang links. ',
|
||||
)
|
||||
.optional(),
|
||||
|
||||
/**
|
||||
* Whether to use the host language parameter for the default locale.
|
||||
*
|
||||
* This options helps keep URLs for your default language clean, however it is
|
||||
* not correct, and is not recommended, because lack of the host language
|
||||
* parameter tells VIntl to use the user locale (whether it's the one they've
|
||||
* choosen before or their browser's one).
|
||||
*/
|
||||
defaultLocaleHasParameter: t.boolean().default(true),
|
||||
|
||||
/** Whether to enable `x-default` hreflang. */
|
||||
xDefaultHreflang: t.boolean().default(true),
|
||||
})
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
"allowImportingTsExtensions": true,
|
||||
|
||||
"types": ["./types/nuxt"],
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
|
||||
"module": "ESNext",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
|
||||
"types": ["./types/nuxt"],
|
||||
|
||||
+33
@@ -64,4 +64,37 @@ declare module '@vintl/nuxt-runtime/options' {
|
||||
* descriptor.
|
||||
*/
|
||||
export const parserless: boolean
|
||||
|
||||
export interface SEOOptions {
|
||||
/**
|
||||
* Whether SEO features are enabled.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled: boolean
|
||||
|
||||
/**
|
||||
* Name of the host language URL parameter, used to override the default or
|
||||
* user language.
|
||||
*
|
||||
* @default 'hl' // For example, ?hl=en to use English locale (if defined).
|
||||
*/
|
||||
hostLanguageParameter: string
|
||||
|
||||
/**
|
||||
* Base URL at which the site will be available. This is required to
|
||||
* generate absolute hreflang links. Without this, VIntl must try to guess
|
||||
* the URL based on the server request URL / current `window.location`.
|
||||
*/
|
||||
baseURL?: string
|
||||
|
||||
/** Whether to use the host language parameter for the default locale. */
|
||||
defaultLocaleHasParameter: boolean
|
||||
|
||||
/** Whether to enable `x-default` hreflang. */
|
||||
xDefaultHreflang: boolean
|
||||
}
|
||||
|
||||
/** SEO options. */
|
||||
export const seo: SEOOptions
|
||||
}
|
||||
|
||||
Generated
+3097
-1720
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -7,8 +7,11 @@
|
||||
"@vintl/nuxt#lint": {
|
||||
"outputs": [".eslintcache"]
|
||||
},
|
||||
"@vintl-dev/pg#intl:extract": {
|
||||
"outputs": ["./locales/en-US.json"]
|
||||
},
|
||||
"@vintl-dev/pg#dev:prepare": {
|
||||
"dependsOn": ["@vintl/nuxt#build"],
|
||||
"dependsOn": ["@vintl/nuxt#build", "@vintl-dev/pg#intl:extract"],
|
||||
"inputs": ["./package.json", "./nuxt.config.ts"],
|
||||
"outputs": [".nuxt"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user