diff --git a/README.md b/README.md index 197d631a2..82f669882 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Nuxt Content reads the `content/` directory in your project, parses `.md`, `.yml - Table of contents generation - Also handles CSV, YAML and JSON(5) - Extend with hooks and content plugins +- I18n support - [...and more](https://content.nuxtjs.org) ## Nuxt 2 diff --git a/docs/content/4.api/3.configuration.md b/docs/content/4.api/3.configuration.md index efc24a46e..ad5af46e5 100644 --- a/docs/content/4.api/3.configuration.md +++ b/docs/content/4.api/3.configuration.md @@ -376,6 +376,8 @@ Can be set to `false` to disable the feature completely. List of locale codes. This codes will be used to detect contents locale. +Checkout the `playground/i18n` example for more details. + ## `defaultLocale` - Type: `String`{lang=ts} diff --git a/package.json b/package.json index abad344a4..74ba19d36 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ ], "scripts": { "dev": "./scripts/playground.sh", + "dev:i18n": "./scripts/playground.sh i18n", "dev:build": "nuxi build playground/basic", "prepare": "nuxi prepare playground/basic", "dev:fixtures": "./scripts/fixture.sh", diff --git a/playground/i18n/app.vue b/playground/i18n/app.vue new file mode 100644 index 000000000..2b49a6a15 --- /dev/null +++ b/playground/i18n/app.vue @@ -0,0 +1,7 @@ + diff --git a/playground/i18n/components/LocaleSwitcher.vue b/playground/i18n/components/LocaleSwitcher.vue new file mode 100644 index 000000000..61ea4d95f --- /dev/null +++ b/playground/i18n/components/LocaleSwitcher.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/playground/i18n/content/en/about.md b/playground/i18n/content/en/about.md new file mode 100644 index 000000000..449429d43 --- /dev/null +++ b/playground/i18n/content/en/about.md @@ -0,0 +1 @@ +# About diff --git a/playground/i18n/content/en/index.md b/playground/i18n/content/en/index.md new file mode 100644 index 000000000..1a7557e04 --- /dev/null +++ b/playground/i18n/content/en/index.md @@ -0,0 +1,5 @@ +--- +title: Home +--- + +# Hello World diff --git a/playground/i18n/content/en/resources/_dir.yml b/playground/i18n/content/en/resources/_dir.yml new file mode 100644 index 000000000..050b3efc0 --- /dev/null +++ b/playground/i18n/content/en/resources/_dir.yml @@ -0,0 +1,3 @@ +title: 'Resources' +navigation: + hello: true diff --git a/playground/i18n/content/en/resources/case-studies.md b/playground/i18n/content/en/resources/case-studies.md new file mode 100644 index 000000000..6deb35c71 --- /dev/null +++ b/playground/i18n/content/en/resources/case-studies.md @@ -0,0 +1,2 @@ +# Case studies + diff --git a/playground/i18n/content/en/resources/index.md b/playground/i18n/content/en/resources/index.md new file mode 100644 index 000000000..f1804eaee --- /dev/null +++ b/playground/i18n/content/en/resources/index.md @@ -0,0 +1,5 @@ +--- +navigation: false +--- + +# Resources diff --git a/playground/i18n/content/zh/about.md b/playground/i18n/content/zh/about.md new file mode 100644 index 000000000..7091a0187 --- /dev/null +++ b/playground/i18n/content/zh/about.md @@ -0,0 +1 @@ +# 关于 diff --git a/playground/i18n/content/zh/index.md b/playground/i18n/content/zh/index.md new file mode 100644 index 000000000..54849d7f6 --- /dev/null +++ b/playground/i18n/content/zh/index.md @@ -0,0 +1,5 @@ +--- +title: 首页 +--- + +# 你好世界 diff --git a/playground/i18n/content/zh/resources/_dir.yml b/playground/i18n/content/zh/resources/_dir.yml new file mode 100644 index 000000000..7dab78ce4 --- /dev/null +++ b/playground/i18n/content/zh/resources/_dir.yml @@ -0,0 +1,3 @@ +title: '资源' +navigation: + hello: true diff --git a/playground/i18n/content/zh/resources/case-studies.md b/playground/i18n/content/zh/resources/case-studies.md new file mode 100644 index 000000000..abafe9b6f --- /dev/null +++ b/playground/i18n/content/zh/resources/case-studies.md @@ -0,0 +1,2 @@ +# 案例研究 + diff --git a/playground/i18n/content/zh/resources/index.md b/playground/i18n/content/zh/resources/index.md new file mode 100644 index 000000000..7ccbb582f --- /dev/null +++ b/playground/i18n/content/zh/resources/index.md @@ -0,0 +1,5 @@ +--- +navigation: false +--- + +# 资源 diff --git a/playground/i18n/nuxt.config.ts b/playground/i18n/nuxt.config.ts new file mode 100644 index 000000000..fb3da73c9 --- /dev/null +++ b/playground/i18n/nuxt.config.ts @@ -0,0 +1,20 @@ +import contentModule from '../../src/module' + +export default defineNuxtConfig({ + modules: [ + // @ts-ignore + contentModule + ], + content: { + documentDriven: true, + locales: ['en', 'zh'], + defaultLocale: 'en' + }, + app: { + head: { + htmlAttrs: { + lang: 'en' + } + } + } +}) diff --git a/src/module.ts b/src/module.ts index c8d1ada12..2b4ed39ef 100644 --- a/src/module.ts +++ b/src/module.ts @@ -405,6 +405,7 @@ export default defineNuxtModule({ // Register composables addImports([ { name: 'queryContent', as: 'queryContent', from: resolveRuntimeModule('./composables/query') }, + { name: 'useContentI18n', as: 'useContentI18n', from: resolveRuntimeModule('./composables/contentI18n') }, { name: 'useContentHelpers', as: 'useContentHelpers', from: resolveRuntimeModule('./composables/helpers') }, { name: 'useContentHead', as: 'useContentHead', from: resolveRuntimeModule('./composables/head') }, { name: 'useContentPreview', as: 'useContentPreview', from: resolveRuntimeModule('./composables/preview') }, diff --git a/src/runtime/components/ContentNavigation.vue b/src/runtime/components/ContentNavigation.vue index 30c7fad41..b82f0a8ad 100644 --- a/src/runtime/components/ContentNavigation.vue +++ b/src/runtime/components/ContentNavigation.vue @@ -38,7 +38,6 @@ export default defineComponent({ // If doc driven mode and no query given, re-use the fetched navigation if (!queryBuilder.value && useState('dd-navigation').value) { const { navigation } = useContent() - return { navigation } } const { data: navigation } = await useAsyncData( diff --git a/src/runtime/composables/content.ts b/src/runtime/composables/content.ts index 3c3c8f968..d6caf8344 100644 --- a/src/runtime/composables/content.ts +++ b/src/runtime/composables/content.ts @@ -37,7 +37,6 @@ export const useContent = () => { const { navigation, pages, surrounds, globals } = useContentState() const _path = computed(() => withoutTrailingSlash(useRoute().path)) - /** * Current `page` key, computed from path and content state. */ diff --git a/src/runtime/composables/contentI18n.ts b/src/runtime/composables/contentI18n.ts new file mode 100644 index 000000000..cbe2b0019 --- /dev/null +++ b/src/runtime/composables/contentI18n.ts @@ -0,0 +1,38 @@ +export const useContentI18n = () => { + const parseLocale = (_path) => { + const { content } = useRuntimeConfig() + const { defaultLocale, locales } = content + + let _locale = defaultLocale || locales[0] + + const pathArr = _path.split('/') + const localeInPath = pathArr[1] + if (locales.includes(localeInPath)) { + _locale = localeInPath + _path = pathArr.join('/').substring(`/${_locale}`.length) + } + + return { + _path, + _locale + } + } + + const getLocaleSwitcherLinkList = (path) => { + const { content } = useRuntimeConfig() + const { defaultLocale, locales } = content + const { _path, _locale } = parseLocale(path) + return locales.map((locale) => { + return { + to: locale === defaultLocale ? _path : `/${locale}${_path}`, + isCurrent: _locale === locale, + locale + } + }) + } + + return { + parseLocale, + getLocaleSwitcherLinkList + } +} diff --git a/src/runtime/composables/navigation.ts b/src/runtime/composables/navigation.ts index 96fa7276d..0491d98b7 100644 --- a/src/runtime/composables/navigation.ts +++ b/src/runtime/composables/navigation.ts @@ -32,7 +32,7 @@ export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | Query return generateNavigation(params) } - const data = await $fetch(apiPath as any, { + let data = await $fetch(apiPath as any, { method: 'GET', responseType: 'json', params: content.experimental.stripQueryParameters @@ -49,5 +49,18 @@ export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | Query throw new Error('Not found') } + const { defaultLocale } = content + const queryLocale = params.where?.find(w => w._locale)?._locale + if (defaultLocale && defaultLocale !== queryLocale) { + const addLocalePrefix = (item) => { + item._path = `/${queryLocale}${item._path}` + if (item.children?.length > 0) { + item.children = item.children.map(addLocalePrefix) + } + return item + } + data = data.map(addLocalePrefix) + } + return data } diff --git a/src/runtime/plugins/documentDriven.ts b/src/runtime/plugins/documentDriven.ts index eb5c16389..14a8a180c 100644 --- a/src/runtime/plugins/documentDriven.ts +++ b/src/runtime/plugins/documentDriven.ts @@ -8,6 +8,7 @@ import { useContentState } from '../composables/content' import { useContentHelpers } from '../composables/helpers' import { fetchContentNavigation } from '../composables/navigation' import { queryContent } from '../composables/query' +import { useContentI18n } from '../composables/contentI18n' // @ts-ignore import layouts from '#build/layouts' @@ -61,7 +62,6 @@ export default defineNuxtPlugin((nuxt) => { // Normalize route path const _path = withoutTrailingSlash(to.path) - // Promises array to be executed all at once const promises: (() => Promise | any)[] = [] @@ -71,11 +71,15 @@ export default defineNuxtPlugin((nuxt) => { */ if (moduleOptions.navigation && routeConfig.navigation !== false) { const navigationQuery = () => { - const { navigation } = useContentState() + let query = { } - if (navigation.value && !dedup) { return navigation.value } + const _locale = to.meta?.documentDriven?.page?._locale + if (_locale) { + query = { where: [{ _locale }] } + } - return fetchContentNavigation() + const queryBuilder = queryContent(query) + return fetchContentNavigation(queryBuilder) .then((_navigation) => { navigation.value = _navigation return _navigation @@ -146,6 +150,7 @@ export default defineNuxtPlugin((nuxt) => { if (typeof routeConfig.page === 'object') { where = routeConfig.page } + const pageQuery = () => { const { pages } = useContentState() @@ -252,6 +257,26 @@ export default defineNuxtPlugin((nuxt) => { } // Route middleware + addRouteMiddleware((to) => { + const { content } = useRuntimeConfig() + + if (!content || !(content?.locales?.length > 0)) { + return + } + + const { parseLocale } = useContentI18n() + const { _path, _locale } = parseLocale(to.path) + + const page = { + _path, + _locale + } + + to.meta.documentDriven = { + page, + navigation: true + } + }) addRouteMiddleware(async (to, from) => { // TODO: Remove this (https://github.com/nuxt/framework/pull/5274) if (to.path.includes('favicon.ico')) { return }