johannschopplich / cacao-kit-frontend

🍫 Best practice Nuxt and KQL starter for your headless Kirby CMS
https://cacao-kit.byjohann.dev
MIT License
30 stars 3 forks source link

Retrieving language variables from Kirby #7

Closed antoine3000 closed 5 months ago

antoine3000 commented 5 months ago

Hello,

Is there a way to use Kirby's language variables, rather than having to duplicate them in Nuxt?

Thanks!

johannschopplich commented 5 months ago

Sure, you can create a API route in the backend:

use JohannSchopplich\Headless\Api\Api;
use JohannSchopplich\Headless\Api\Middlewares;
use Kirby\Cms\App;
use Kirby\Cms\Language;

return [
    'routes' => function (App $kirby) {
        return [
            [
                'pattern' => '__translations__',
                'method' => 'GET',
                'auth' => false,
                'action' => Api::createHandler(
                    [Middlewares::class, 'hasBearerToken'],
                    function () use ($kirby) {
                        $languages = $kirby
                            ->languages()
                            ->map(
                                fn (Language $language) => [
                                    'code' => $language->code(),
                                    'translations' => $language->translations()
                                ]
                            )
                            ->values();

                        return Api::createResponse(200, $languages);
                    }
                )
            ]
        ];
    }
];

And then create a module in the frontend under modules/translation-prefetch.ts that prefetches your language at build-time (will be run by Nuxt automatically, ne need to add it to your Nuxt config):

import { writeFile } from 'node:fs/promises'
import { joinURL } from 'ufo'
import { ofetch } from 'ofetch'
import type { KirbyApiResponse } from 'kirby-types'
import { defineNuxtModule, useLogger } from 'nuxt/kit'

type KirbyTranslationsResponse = KirbyApiResponse<
  {
    code: string
    translations: Record<string, string>
  }[]
>

export default defineNuxtModule({
  meta: {
    name: 'translations-prefetch',
  },
  async setup(options, nuxt) {
    const logger = useLogger('translations-prefetch')
    const start = Date.now()
    const $kirby = ofetch.create({
      baseURL: process.env.KIRBY_BASE_URL,
      headers: {
        Authorization: `Bearer ${process.env.KIRBY_API_TOKEN}`,
      },
    })

    try {
      const response = await $kirby<KirbyTranslationsResponse>(
        'api/__translations__',
      )

      for (const language of response.result!) {
        const nestedTranslations = flatToNestedTranslations(
          language.translations,
        )
        await writeFile(
          joinURL(nuxt.options.rootDir, 'locales', `${language.code}.json`),
          JSON.stringify(nestedTranslations, null, 2),
          'utf-8',
        )
      }

      logger.info(
        `Prefetched Kirby language translations ${Date.now() - start}ms`,
      )
    } catch (error) {
      logger.error(error)
      logger.error('Failed to prefetch Kirby language translations')
    }
  },
})

function flatToNestedTranslations(
  translations: Record<string, string>,
): Record<string, any> {
  const nestedObject: Record<string, any> = {}

  for (const [key, value] of Object.entries(translations)) {
    let currentLevel = nestedObject // Start at the root of the nested object
    const parts = key.split('.')
    for (const [index, part] of parts.entries()) {
      if (index === parts.length - 1) {
        // If we're at the last part, assign the value
        currentLevel[part] = value
      } else {
        // If we're not at the last part, either use the existing object or create a new one
        currentLevel[part] = currentLevel[part] || {}
        // Move down one level in the nested structure
        currentLevel = currentLevel[part]
      }
    }
  }

  return nestedObject
}

Now, all your locales are available in the frontend. Make sure to exclude the prefetched files from your Git repo.

antoine3000 commented 5 months ago

It works perfectly, huge thanks @johannschopplich!