Skip to content
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
2 changes: 1 addition & 1 deletion src/app/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ router.beforeEach((to, from) => {
color="neutral"
variant="outline"
class="bg-transparent backdrop-blur-md px-2"
label="Edit this page"
:label="$t('studio.buttons.edit')"
@click="editContentFile(activeDocuments[0].id)"
/>
</UFieldGroup>
Expand Down
15 changes: 15 additions & 0 deletions src/app/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createI18n, type LocaleMessages, type VueMessageType } from 'vue-i18n'

const defaultLocale = navigator.language.split('-')[0] || 'en'

type StudioMessages = Record<string, LocaleMessages<VueMessageType>>

export function createStudioI18n(messages: Record<string, unknown>) {
return createI18n({
legacy: false,
locale: defaultLocale,
fallbackLocale: 'en',
messages: messages as StudioMessages,
globalInjection: true, // to be able to use $t() in templates
})
}
7 changes: 7 additions & 0 deletions src/app/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"studio": {
"buttons": {
"edit": "Edit this page"
}
}
}
7 changes: 7 additions & 0 deletions src/app/src/locales/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"studio": {
"buttons": {
"edit": "Editer la page"
}
}
}
7 changes: 7 additions & 0 deletions src/app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Media from './pages/media.vue'
import Review from './pages/review.vue'
import Success from './pages/success.vue'
import Error from './pages/error.vue'
import { createStudioI18n } from './i18n'

if (typeof window !== 'undefined' && 'customElements' in window) {
const NuxtStudio = defineCustomElement(
Expand Down Expand Up @@ -55,6 +56,12 @@ if (typeof window !== 'undefined' && 'customElements' in window) {
})

app.use(router)
// Read messages from window (set by our new Nuxt plugin)
// @ts-expect-error - We are defining this global variable
const messages = window.__NUXT_STUDIO_I18N_MESSAGES__ || {}
// Create the i18n instance dynamically
const i18n = createStudioI18n(messages)
app.use(i18n) // Use the new instance
// app._context.provides.usehead = true
app.use({
install() {
Expand Down
89 changes: 87 additions & 2 deletions src/module/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { defineNuxtModule, createResolver, addPlugin, extendViteConfig, useLogger, addServerHandler, addTemplate, addVitePlugin } from '@nuxt/kit'
import { createHash } from 'node:crypto'
import { defu } from 'defu'
import { resolve } from 'node:path'
import { resolve, basename } from 'node:path'
import { promises as fsp } from 'node:fs'
import { globby } from 'globby'
import fsDriver from 'unstorage/drivers/fs'
import { createStorage } from 'unstorage'
import type { ViteDevServer } from 'vite'
import type { ViteDevServer, Plugin } from 'vite' // Import Plugin
import { getAssetsStorageDevTemplate, getAssetsStorageTemplate } from './templates'
import { version } from '../../../package.json'

Expand Down Expand Up @@ -77,6 +79,21 @@ interface ModuleOptions {
development?: {
sync?: boolean
}
/**
* i18n settings for the Studio.
*/
i18n?: {
/**
* The default locale to use.
* @default 'en'
*/
defaultLocale?: string
/**
* Directly override or add translations.
* @default {}
*/
translations?: Record<string, unknown>
}
}

const logger = useLogger('nuxt-studio')
Expand Down Expand Up @@ -106,11 +123,79 @@ export default defineNuxtModule<ModuleOptions>({
development: {
sync: false,
},
i18n: {
defaultLocale: 'en',
translations: {},
},
},
async setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
const runtime = (...args: string[]) => resolver.resolve('./runtime', ...args)

// --- START: I18N LOGIC ---
const defaultLocalesPath = resolver.resolve('../../app/src/locales')
const defaultLocaleFiles = await globby(`${defaultLocalesPath}/*.json`)
const defaultMessages: Record<string, unknown> = {}
for (const file of defaultLocaleFiles) {
const lang = basename(file, '.json')
defaultMessages[lang] = JSON.parse(await fsp.readFile(file, 'utf-8'))
}

const userLocalesPath = resolve(nuxt.options.srcDir, 'locales/studio')
const userLocaleFiles = await globby(`${userLocalesPath}/*.json`)
const userMessages: Record<string, unknown> = {}
for (const file of userLocaleFiles) {
const lang = basename(file, '.json')
userMessages[lang] = JSON.parse(await fsp.readFile(file, 'utf-8'))
}

const optionsMessages = options.i18n?.translations || {}
// @ts-expect-error - nuxt.options.appConfig.studio is not fully typed
const appConfigMessages = nuxt.options.appConfig.studio?.i18n?.translations || {}

const finalMessages = defu(
optionsMessages, // 1. Highest priority (nuxt.config options)
appConfigMessages, // 2. Priority (app.config)
userMessages, // 3. Priority (locales/studio folder)
defaultMessages, // 4. Default translations (module)
)

// --- VITE PLUGIN ---
// This plugin provides the messages to be imported *by our new Nuxt plugin*
const virtualModuleName = 'virtual:studio-i18n-messages'
const resolvedVirtualModuleId = '\0' + virtualModuleName

addVitePlugin({
name: 'nuxt-studio-i18n-virtual-module',
resolveId(id) {
if (id === virtualModuleName) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export default ${JSON.stringify(finalMessages)}`
}
},
} as Plugin)

// This plugin will run in the Nuxt context, import the virtual messages,
// and attach them to the window.
addTemplate({
filename: 'studio-i18n-plugin.client.mjs',
getContents: () => `
import messages from 'virtual:studio-i18n-messages'

export default defineNuxtPlugin(() => {
// @ts-ignore
window.__NUXT_STUDIO_I18N_MESSAGES__ = messages
})
`,
})

// Register the new plugin
addPlugin(resolve(nuxt.options.buildDir, 'studio-i18n-plugin.client.mjs'))

if (!nuxt.options.dev) {
options.development!.sync = false
}
Expand Down
Loading