Using Sanity.io and Nuxt.js for a complete localization solution

Deniz Gençtürk
4 min readOct 14, 2019

--

When it comes to figuring out a way to structure localization on any website we usually have to find two separate solutions: One for dynamic content which might be for example products or product models on an e-commerce website, and one for static content like menu items, footer text, button texts, etc.

The Problem

The problem is that usually, these two things are configured elsewhere and we need different logic to both store and display these localizations. Usually we end up needing separate dashboards too which makes everything harder to maintain and understand for both translators and developers.

In this post we will look at a way we can configure nuxt to use Sanity CMS as a single source for all translation assets we need. We will also see how Sanity can be utilized as a single dashboard for our translators and content creators.

Configuring nuxt.js and the nuxt-i18n module

Make sure you have installed nuxt-i18n. You can follow the documentation here: https://nuxt-community.github.io/nuxt-i18n/setup.html

For the configuration we will need the lazy option to be set to true, the lang folder defined, the locales to be set and vuex to be configured as below. (Note that I’m using version 6 of nuxt-i18n so the config syntax might differ depending on your version.)

nuxt.config.js

/*
** Nuxt.js modules
*/
modules: [
// ...other modules,
[
'nuxt-i18n',
{
locales: [
{
code: 'en',
file: 'en.js',
iso: 'en-US',
name: 'English'
},
{
code: 'de',
file: 'de.js',
iso: 'de-DE',
name: 'Deutsch'
}
],
defaultLocale: 'en',
lazy: true,
langDir: 'lang/',
vuex: {
moduleName: 'i18n',
syncLocale: true,
syncMessages: true,
syncRouteParams: true
}
}
]
]

Let’s start with dynamic content

To handle translations of dynamic content I like to use the preferred method described on sanity docs here: https://www.sanity.io/docs/localization

This creates your documents with fields in the form of `${fieldName}.${langCode}` and also renders nice collapsible translation menus on the dashboard.

To display this content on our nuxt project we can use the localize method provided at the end of the same page. Instead of importing and using this function everywhere though I find it much more convenient to implement it in the store. It works very well with nuxt i18n’s vuex module. Let’s quickly see how that works:

./localize.js

export default function localize(value, languages) {
if (Array.isArray(value)) {
return value.map(v => localize(v, languages))
} else if (typeof value === 'object') {
if (/^locale[A-Z]/.test(value._type)) {
const language = languages.find(lang => value[lang])
return value[language]
}
return Object.keys(value).reduce((result, key) => {
result[key] = localize(value[key], languages)
return result
}, {})
}
return value
}

./store/index.js

import localize from '@/localize'export const state = () => ({
products: []
})
// getters
export const getters = {
lang: state => [state.i18n ? state.i18n.locale : 'de', 'en'],
products: (state, getters) => localize(state.products, getters.lang)
}

Inside the getter function, we define the language logic we want: In this case, use the i18n locale if the i18n is defined in the state. In case it is not defined use ‘de’ messages. And as a fallback always use ‘en’ messages.

That's basically all you need to do to get dynamic translations working. You can implement the same logic elsewhere without going through the store too.

Static translations

First, we will need a document on sanity to hold all of our translation messages with the following fields:

  • key: The translation key we will use inside vue-i18n’s $t method
  • value: A localeString type field that will hold the messages
  • description: An optional field we can use to add notes for translators.

sanity-project/schemas/i18n/message.js


import FaLanguage from 'react-icons/lib/fa/language';
export default {
name: 'message',
title: 'Translation Messages',
icon: FaLanguage,
type: 'document',
fields: [
{
name: 'description',
title: 'Description',
type: 'string',
},
{
name: 'key',
title: 'System Key',
type: 'string',
},
{
name: 'value',
title: 'Value',
type: 'localeString'
}
],
preview: {
select: {
title: 'description',
subtitle: 'value.en'
}
}
}

This is how it will render in the dashboard:

Fetching static translations

Since we configured nuxt to look for the lang/ folder for the messages here is where we will do the fetching. Luckily nuxt accepts async functions here so we can fetch the relevant language from the sanity client when messages are needed.

Let’s first write a function that fetches the language and creates the key-value pair structure we need to present to the i18n module.

/get_messages.js

import { client } from '@/sanity.js'
export default async (lang) => {
const result = await client.fetch(`
*[_type == 'message']{
key, 'value': value['${lang}']
}
`)
const messages = {}
result.forEach((message) => {
messages[message.key] = message.value
})
return messages
}

Then it’s as simple as creating a js file with the lang name for each language with support with the code below:

/lang/de.js

import getMessages from '@/get-messages'export default (context) => {
return new Promise(function (resolve) {
resolve(getMessages('de'))
})
}

That’s it! Now we can use the $t helper to translate any message we have defined in sanity. {{ $t(‘main.contact’) }} will yield the value of the current language.

I did not try to implement a fallback language method here so vue-i18n will display the key if a specific translation value is missing and throw a warning in the console. If anyone has a good idea for a fallback method please share it in the comments!

Keep reading

In this next post, we will take a look at building a sitemap with localized dynamic routes and how to provide this data to the “nuxt generate” command for SSR applications.

--

--