Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add i18n support #2075

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/content/4.api/3.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions playground/i18n/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<div>
<LocaleSwitcher />
<ContentNavigation class="navigation" />
<NuxtPage />
</div>
</template>
32 changes: 32 additions & 0 deletions playground/i18n/components/LocaleSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
const route = useRoute()

const localeLinks = ref([])
const { getLocaleSwitcherLinkList } = useContentI18n()
const labelMap = {
en: 'English',
zh: 'δΈ­ζ–‡'
}
watchEffect(
() => {
localeLinks.value = getLocaleSwitcherLinkList(route.path)
}
)
</script>

<template>
<div>
<NuxtLink v-for="item in localeLinks" :key="item.to" :to="item.to" :class="{current: item.isCurrent }">
{{ labelMap[item.locale] }}
</NuxtLink>
</div>
</template>

<style scoped>
a{
margin-right: 5px;
}
a.current{
text-decoration: none;
}
</style>
1 change: 1 addition & 0 deletions playground/i18n/content/en/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# About
5 changes: 5 additions & 0 deletions playground/i18n/content/en/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Home
---

# Hello World
3 changes: 3 additions & 0 deletions playground/i18n/content/en/resources/_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title: 'Resources'
navigation:
hello: true
2 changes: 2 additions & 0 deletions playground/i18n/content/en/resources/case-studies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Case studies

5 changes: 5 additions & 0 deletions playground/i18n/content/en/resources/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
navigation: false
---

# Resources
1 change: 1 addition & 0 deletions playground/i18n/content/zh/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ε…³δΊŽ
5 changes: 5 additions & 0 deletions playground/i18n/content/zh/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: ι¦–ι‘΅
---

# δ½ ε₯½δΈ–η•Œ
3 changes: 3 additions & 0 deletions playground/i18n/content/zh/resources/_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title: '衄源'
navigation:
hello: true
2 changes: 2 additions & 0 deletions playground/i18n/content/zh/resources/case-studies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# ζ‘ˆδΎ‹η ”η©Ά

5 changes: 5 additions & 0 deletions playground/i18n/content/zh/resources/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
navigation: false
---

# 衄源
20 changes: 20 additions & 0 deletions playground/i18n/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -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'
}
}
}
})
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ export default defineNuxtModule<ModuleOptions>({
// 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') },
Expand Down
1 change: 0 additions & 1 deletion src/runtime/components/ContentNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<NavItem[]>(
Expand Down
1 change: 0 additions & 1 deletion src/runtime/composables/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
38 changes: 38 additions & 0 deletions src/runtime/composables/contentI18n.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
15 changes: 14 additions & 1 deletion src/runtime/composables/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
33 changes: 29 additions & 4 deletions src/runtime/plugins/documentDriven.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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> | any)[] = []

Expand All @@ -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
Expand Down Expand Up @@ -146,6 +150,7 @@ export default defineNuxtPlugin((nuxt) => {
if (typeof routeConfig.page === 'object') {
where = routeConfig.page
}

const pageQuery = () => {
const { pages } = useContentState()

Expand Down Expand Up @@ -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 }
Expand Down