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

Support I18n #58

Closed
narr07 opened this issue Feb 16, 2025 · 15 comments
Closed

Support I18n #58

narr07 opened this issue Feb 16, 2025 · 15 comments
Assignees

Comments

@narr07
Copy link

narr07 commented Feb 16, 2025

I wish it could have a language changer feature

@rusbers
Copy link

rusbers commented Feb 16, 2025

That would be a great feature for this template

@alioftech
Copy link

This would be amazing!

@nuotsu
Copy link
Owner

nuotsu commented Feb 22, 2025

Hi everyone, here's the approach I'm considering and would appreciate your opinions/feedback.

Firstly, i18n is complex and there's no easy and correct way of implementing (my excuse haha 😅). However, it's been frequently requested so I'll be taking a stab at it.

I'm deciding on a "page-level" localization approached, as opposed to a "field-level" one. Setting up a translation input/textarea for all/some fields would be way too much (for me and for users). I'd rather just have users duplicate a page, and replace the necessary values with the updated translations.

PROs:

  • easy as duplicating documents to add new translations
  • no need to set up translation fields

CONs:

  • content changes in either the original or translated would have to be copied over.

Proposed language document

Image

Proposed page.language and page.original fields

I'm thinking to add a language and original field for Page and Blog post documents, that reference a Language document from above and the original ("translated from") document, respectively.

Image

Proposed URL pattern

I assume we all can agree on the URL structure for translated pages would be to prepend the language code. I will also add the HTML lang attribute to the <body> tag upon implementation.

https://mywebsite.com/<lang>/<parent>/<slug>

# e.g.
https://sanitypress.dev/ja/  # index
https://sanitypress.dev/en-US/docs
https://sanitypress.dev/blog/fr/my-sample-blog-post

FYI — page.parent was introduced in v6.4.0


Do let me know your thoughts, suggestions, possible concerns, etc. I'm all ears👂

@nuotsu nuotsu added the ⏳ pending Awaiting response from issuer(s) label Feb 22, 2025
@alioftech
Copy link

alioftech commented Feb 22, 2025

Just sharing my experience with a non sanity translation project I worked on recently. That is www.xaxameme.com

The website supports about 5 languages and I had a few challenges with the localisation.

There are some languages that are RTL like Arabic so I needed to support that in the code. When the locals was Arabic. Later I used the next-intl package which was not optimised for nextjs 15 which made some things difficult. Also supporting multiple sitemaps for each language was also a thing.

I did research doing this for sanity and I was looking at the @sanity/document-internationalization as it's the official one. I imagine that there would just be some minor changes to the code base needed. Using an official plugin might be better than maintaining the functionality alone ?

Btw I'm happy to test and feedback the languages I know when gone a bit further

@nuotsu
Copy link
Owner

nuotsu commented Feb 23, 2025

Thanks so much for your feedback @alioftech!

I've just pushed a new branch i18n (see commits here) using the @sanity/document-internationalization plugin.

Changelog

  • ⚠️ Breaking: using optional catch-all route (app/[[...slug]]/page.tsx) 3d42adb
    • replaces the page.tsx (homepage) and [...slug]/page.tsx (dynamic route)
  • add lang attribute to the <html> tag c8f65ce
  • Language Switcher (added to the site footer) 7d66d37
    • adds a lang cookie for use in middleware
  • middleware for auto-redirecting existing/non-existing pages c83b375
  • auto-generated hreflangs meta tags 3ee9dd5

Config

// src/lib/i18n.ts

export const supportedLanguages = [
  { id: 'en', title: 'English' },
  { id: 'ja', title: 'Japanese' },
  // { id: 'fr', title: 'French' },
] as const satisfies Language[]
// sanity.config.ts

import { supportedLanguages } from '@/lib/i18n'
import { documentInternationalization } from '@sanity/document-internationalization'

export default defineConfig({
  plugins: [
    // ...
    documentInternationalization({
      supportedLanguages,
      schemaTypes: ['page', 'blog.post'],
    }),
  ],
})

Screenshots

@sanity/document-internationalization plugin

Image

Language Switcher component in the footer:

Image

If all looks good and I get some more feedback, if any, I prepare to be added in the next major version of Sanity (v6.5 or v7.0?)

@wild2008
Copy link

wild2008 commented Feb 24, 2025

@nuotsu I had the pleasure of seeing your work on i18n today, and I think it is definitely very good and very big news because you listened to the voice of the community.

I agree with your idea of ​​unified page-level translation. For i18n, I have a few expectations for your reference:

  1. For the default language, remove the language prefix in the URL. For example: English is the default language, use /about directly instead of /en/about.

  2. Different languages ​​of a page may use different slugs, such as English: /product/product-name, French: /fr/produit/nom-du-produit.

  3. After clicking the language switcher on a certain page, switch to the URL of the corresponding language. If it does not exist, jump to the homepage of the corresponding language.

  4. For the URL of Global modules, I prefer:

https://sanitypress.dev/blog/fr/my-sample-blog-post

to >

https://sanitypress.dev/fr/bloguer/mon-exemple-de-blog-post

In addition, for the text labels on the page, how are they translated? For example, there is a file similar to messages/en.json in next-intl.

Finally, if you can set up multiple languages ​​like this, does it mean that you only need to maintain one branch? Because it can support both one language and multiple languages.

@nuotsu
Copy link
Owner

nuotsu commented Feb 25, 2025

@wild2008 Thanks for your detailed feedback! To answer your points:

  1. For the default language, remove the language prefix in the URL. For example: English is the default language, use /about directly instead of /en/about.

That's my intention:

  • if en is the default, the URL should not be prefixed.
  • if a users visits a URL with the default prefix (/en/blog), the middleware should redirect to the unprefixed URL (/blog)

(FYI the default defined as: the first item in the supportedLanguages config)

  1. Different languages ​​of a page may use different slugs, such as English: /product/product-name, French: /fr/produit/nom-du-produit.

I've updated the LanguageSwitcher component and the middleware to account for this.

You can now set different URLs for the same page, for each language. 12b7206

  1. After clicking the language switcher on a certain page, switch to the URL of the corresponding language. If it does not exist, jump to the homepage of the corresponding language.

My personal take: as a user I would want to stay on the same page even if the translation doesn't exist, rather than being redirected to the Homepage.

I added a disabled state to the LanguageSwitcher options, for non-existent translations, so at least they know the translation is not available. d728be5

Image
  1. For the URL of Global modules, I prefer: //blog/*

Setting a different name for the /blog/ portion of URLs is rather tricky... I'd need to set up separate dynamic routes for each occasion. At that point, I'd have the developers set that up themselves. I'm planning to write a detailed blog post for advanced configurations that could include this setup.

In addition, for the text labels on the page, how are they translated? For example, there is a file similar to messages/en.json in next-intl.

If you are referring to the actual translations, those are done within the actual page document. It's essentially a duplicate of the original page/blog.post document so all the existing fields/labels are there to be updated.

Finally, if you can set up multiple languages ​​like this, does it mean that you only need to maintain one branch? Because it can support both one language and multiple languages.

I can maintain this as a single branch. Everything should still work whether additional languages or zero languages are configured in the src/lib/i18n.ts file. ceec7ae

@alioftech
Copy link

Looking great!

@wild2008
Copy link

wild2008 commented Feb 26, 2025

@nuotsu I have to say that your solution is very practical. Only about point 3, a practical scenario is:

A visitor enters a landing page, he finds the entrance to other languages ​​through the language switcher, then he only needs to click to enter the website in other languages ​​(if there is a translation of the current page, it is better, if not, he can also view other pages in that language).

If the status is set to unavailable, the language switcher loses the function of the international version entrance.

@nuotsu
Copy link
Owner

nuotsu commented Feb 26, 2025

@wild2008 Thanks again for reviewing! I do recognize the advantages of your approach. I will investigate the possibility of implementing options in the middleware to accommodate both approaches.

FYI, the LanguageSwitcher component sets a language cookie when an alternative language is selected. The middleware will read this cookie and display the translated page, falling back to the default language if it is not present.

In the event that the user navigates to subsequent pages, the middleware will persist in attempting to display the translated page (the selected language stored in the cookie).

As soon as the LanguageSwitcher is invoked and an alternative language is selected, the aforementioned process will be repeated.

@wild2008
Copy link

@nuotsu I mentioned earlier that there are files like message/en.json in the next-intl project. My question is: for example, text like "Page not found" has different expressions in different languages. How to achieve multi-language translation in SanityPress?

@nuotsu
Copy link
Owner

nuotsu commented Feb 27, 2025

@wild2008 I'm not sure if I can find any areas in the codebase where there is static text. All copy should reside in the Sanity Studio, including the 404 page (this is a Page document with slug 404).

That said, I likely won't add a .json based translation in the initial release (this may change in the future if it's requested by more users or if it's needed at all).


@wild2008 On a separate note, I couldn't find any contact details on your profile... Would you kindly reach out to the SanityPress email ([email protected])? I would like to share some special details with you, if you may!

@wild2008
Copy link

@nuotsu I have sent you an email and I am happy to contact you via email.

@nuotsu
Copy link
Owner

nuotsu commented Feb 28, 2025

@wild2008 As for suggestion on translating static text, such as in the Search module etc., I will make the "suggestion" of implementing 3rd party plugins like next-intl. I've prepared a blog post that gives a brief configuration write-up on this.

Image

The example with the Search module gets quite dynamic (since I use concepts like search scope [pages, blog posts, etc]) so storing this in Sanity could get a bit bloated, hence I've put in only in the Next.js frontend. Anything static that can be in Sanity, should be in Sanity for easier translations.

@nuotsu
Copy link
Owner

nuotsu commented Mar 6, 2025

I'm thrilled to announce i18n is now supported in SanityPress v7.0.0. See SanityPress Pro for details.

@nuotsu nuotsu closed this as completed Mar 6, 2025
@github-project-automation github-project-automation bot moved this from In Progress to Done in SanityPress 2049 Mar 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

When branches are created from issues, their pull requests are automatically linked.

6 participants