Skip to content

Cynsar-Foundation/spaces

Repository files navigation


Cynsar Foundation Codebase

Welcome to the official code repository for the cynsar.foundation website. This platform was meticulously developed using DatoCMS.

Special Acknowledgment

We're grateful to Cynsar Capital for their generous donation of a privacy-preserving page tracking code, ensuring our visitors' data is protected and anonymous.

Features

  • DatoCMS Integration: Efficient content management that ensures the website's content is up-to-date and seamlessly displayed.
  • Privacy-Preserving Page Tracking: Courtesy of Cynsar Capital, this tracking code guarantees our visitors’ privacy is always a top priority.
  • Headless Gatsby Starter Template: Headless Multilanguage Starter for Gatsby all thanks and all the code is borrowed from this Live Demo

Installation and Usage

# Clone the repository
git clone https://github.com/Cynsar-Foundation/spaces.git

# Navigate to the project directory
cd cynsar.foundation

# Install dependencies
npm install

# Start the local development server
npm start

Contributions

We welcome any contributions to improve the cynsar.foundation platform. Please read the CONTRIBUTING.md before pushing any changes.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.


Thank you for taking an interest in the Cynsar Foundation. Your support and contributions further our mission.


Further Readme


Headless Multilanguage Starter for Gatsby

The most powerful multilanguage blog starter for Gatsby. Completely headless.

Live Demo


Features

  • 100% Headless: Define languages and translate pages, posts, slugs, SEO meta tags and PWA settings directly on DatoCMS.
  • Language switcher component swapping between different slugs/paths per locale
  • Automatic and easy internal links localization using custom Navigator component
  • User preferred language detection and redirection
  • Built-in support for RTL languages such as Arabic or Hebrew
  • Per-locale PWA webmanifest files generation on build time, dynamically injected according to the page locale.
  • Support for any language code path such as "/en-GB" or "/en"
  • 404 page displaying localized content according to the user preferred language
  • Choose which post or category to translate (and generate) for each locale.
  • Related posts, social sharing and synthax highlighting.
  • Dark mode with CSS variables
  • Built without any internationalization plugin, just Gatsby APIs.

Lighthouse scores

Performance Accessibility Best Pratices SEO PWA
98 ~ 100 100 100 100 8/9

Run Lighthouse Test


Table of contents


Why DatoCMS

It is the most powerful headless CMS out there and also, the only one up to the job. You can define app languages directly in the project administration area and query them via GraphQL as well. Moreover, it has dozens of features that give your content editors superpowers.

Don't worry about the pricing or any other plan restriction, as long as your website will have less than 300 pages (for each locale 😊) you will never exceed the free developer plan. There are no limits on the locales you can configure in your project.

if you are not familiar with DatoCMS, the following links will be useful:

Also, you can enjoy instant previews on Gatsby Cloud, without any crazy futher configuration, just save the draft and preview the content.

⚠️ This project is not mantained by DatoCMS folks neither I am affiliated with them.


Important notes

This starter lets you completely focus on your content by taking care of all the rest.

If you landed on this repo and your goal is just to setup a multilanguage blog with the same content structure of this starter (articles, category pages and archive pages) all you have to do is to follow all the steps of the configuration section.

The 95% of the whole process is done directly on DatoCMS in a super-friendly environment.

Once new content is translated and published, your website will build accordingly and all the features will work out-of-the-box (page generation, language switcher, internal link navigation, SEO meta tags, PWA webmanifest, redirect, right-to-left text support...).

If you want to deeply customize it, like generating pages with different paths, you can visit the section Extending the starter.

In such case, knowledge of GraphQL query variables and Gatsby createPages API is required.


Configuration

1. Starter installation

  1. Install the starter with Gatsby CLI:
gatsby new headless-gatsby-multilang https://github.com/smastrom/headless-gatsby-multilang.git
  1. Log in to your DatoCMS account and click the button below:

    Clone DatoCMS project

  2. In the cloned project administration area, grab the read-only API token and replace it in your gatsby-config.js file:

    {
      resolve: 'gatsby-source-datocms',
      options: {
        apiToken: 'YOUR_API_TOKEN',
      },
    },
  3. Initialize a new Git repo, commit and push all the files.

  4. Sign-in to Gatsby Cloud, add a new site and connect with your Git repo:

Import to Gatsby Cloud

  1. Connect your DatoCMS account to Gatsby Cloud and create the site.

Connect DatoCMS


2. Environment configuration

Please make sure that the development server is not running before following these steps.

1. Access project languages

On DatoCMS navigate to: Settings > Environment > Settings and check for the starter default languages:

2. Remove unwanted languages and pick a fallback language

If your website final language list won't include any of the fully translated starter language codes (en, es-ES or it), pick one of your choice (for example en) and do not delete it, just keep it in the list (for the being) and delete the others.

Otherwise, just delete the unnecessary languages, leave the ones you will use and pick one of your choice.

Starting from now this is your fallback language.

This starter is using fallbacks for ar-AE, so do not consider it as a language you can pick.

⚠️ If for example, you are planning to add English (United Kingdom) "en-GB" and to remove "en", consider "en-GB" as a different language and keep "en" in the list as fallback.

3. Add new languages

Choose any kind of lang code you prefer from the dropdown and add the languages:

4. Set the default language

Drag to the left at first position your website default language, and rearrange in order of importance the others:

💾 Save your project settings.

5. Set the fallbacks

Open gatsby-config.js and set the fallbacks as displayed below:

{
  resolve: 'gatsby-source-datocms',
  options: {
    apiToken: '<YOUR_PUBLIC_API_TOKEN>',
    localeFallbacks: {
      'zh': 'en',
      'pt-BR' : 'en',
    },
  },
},

You should create a property for each new language (not included in the starter) and assign to it the fallback picked before ("en").

💾 Save the file.


3. Environment testing

Local environment

Navigate into your project folder and run gatsby develop. Check on your terminal, at some point you will see something like:

Console infos

By opening http://localhost:8000 you will notice that your languages are now displayed in the switcher, in the same order as you set before in your administration area:

Switcher

As you can see pages are generated with proper paths, SEO meta tags and the webmanifest localized are injected to the head for each language.

Whenever you will change the languages order on Dato, your website will be generated accordingly.

You can switch language and navigate the website. Content will still be displayed in fallback language.


Gatsby Cloud (optional)

Commit and push your changes. Access Gatsby Cloud and monitor the build trigged by your Git connection, it should be ready soon.

Once the build is completed, if you visit your website (via the free domain assigned by Gatsby Cloud) you should be able to see your website live just like you see it locally.

A build will also be triggered by the DatoCMS connection everytime you publish content.


4. Understand content models structure

Before finalizing the starter configuration make sure to have read and understood this section.

I have organized the vertical menu of the content editing area according to the content models' type and purpose:

Gatsby Node models are queried by gatsby-node.js in order to generate pages and also by the templates (used to generate such pages) in order to render the correspondent data fields.

Data-only models are queried in different JSX files to render some data but they are not used to generate any page (hence the name data-only).

Basically, what differs between the two groups is that Gatsby Node models have a localizable slug field used to build the path and a localizable seo field to render the meta tags.

1. Gatsby Node

Single instance

Homepage - Used to generate the homepage:

  • Default language: /
  • Other languages : /it-IT

Categories Archive - Used to generate the categories archive page:

  • Default language: /categories
  • Other languages : /it-IT/categorie

Blog Root - Used to generate the blog root page:

  • Default language: /blog
  • Other languages : /it-IT/articoli

Collections

Other pages - Used to generate pages using a shared template such as /guide and /features

  • Default language: /guide
  • Other languages : /it-IT/guida

Categories - Used to generate category pages:

  • Default language: /blog/react
  • Other languages : /it-IT/articoli/react

Posts - Used to generate post pages:

  • Default language: /blog/react/rendering-elements
  • Other languages : /it-IT/articoli/react/renderizzare-elementi

2. Data-only

Collections

Authors - A collections of records representing the authors of your blog. These records can be linked to any post.

Single instance

Menu - In this content model you can add or remove a menu item. You can order them via drag-n-drop and link to each of them a record of the Gatsby Node group.

Footer - Contains the footer content.

404 Page - 404 page content, fully localizable.

Seo and PWA - Fields related to Global SEO and PWA webmanifest.

Misc strings - String fields used site-wide such as "Recent posts" or "See all categories".


5. Finalizing starter configuration

1. Get rid of blog dummy content

Make sure your development server is not running.

  1. Delete any article / category / author record coming with this starter (do not delete the content models!).
  2. Create and publish your first category and author records and translate the fields for any locale.
  3. Create and publish your first post, translate the fields for any locale and link the category and the author just created to the post.

⚠️ Since the fallback language picked before is still necessary to finalize the configuration, translate also its fields. You can copy-paste from another language or enter some dummy data, it doesn't matter. It will take 30 seconds.

  1. Run gatsby develop and test the outcome

From now on when you will add a category or a post (or you will edit it) the layout will change accordingly and new pages with proper paths will be generated as well. Usually, it takes 2 to 4 seconds to see the edits live on your dev server.


2. Translate the starter base content

Translate any other content models' record and field such as Homepage, Categories archive, Blog root, Global SEO and PWA, Misc text strings etc...

You will always notice which records needs to be translated:

⚠️ Make also sure that you have at least one record for each content model of type collection.


3. Get rid of the starter fallback language

⚠️ You have to follow this step only if you had to pick as fallback a starter language that won't be included in your website language list.

  1. Once fields' content has been translated in any language you added, navigate to your administration area and delete the fallback language picked before from your languages list:

  1. Open gatsby-config.js and delete the fallback properties:
{
  resolve: 'gatsby-source-datocms',
  options: {
    apiToken: '<YOUR_PUBLIC_API_TOKEN>',
    localeFallbacks: { // <-- Delete from here
      zh: 'en',
      'pt-BR' : 'en',
    }, // <-- to here
  },
},

4. Set proper fallbacks (optional)

If your plan is to publish content in default language and to translate other languages later on, set the fallbacks in gatsby-config.js:

{
  resolve: 'gatsby-source-datocms',
  options: {
    apiToken: '<YOUR_PUBLIC_API_TOKEN>',
    localeFallbacks: {
      'pt-BR': 'zh',
    },
  },
},

In this way pages generation won't fail for untranslated records since any untranslated field queried via GraphQL will return the value of the fallback language you just set.

😉 By now, you should be able to add and translate posts, categories and any other record you want and the starter will take care of the rest. Check the next section to have a complete overview on how to handle blog content.


Managing content

Choose which content to generate (and translate)

When you edit records of Posts and Categories content models, you will notice this boolean field:

By setting it to true, the post or category page won't be generated for that language.

This is an example of category not available for "es" language.

This is an example of post not available for "it" and "es" languages.

If for example, the field is set to true for "es" language and the user is viewing that post/category in "en", if he tries to switch to "es", he will be redirected to the blog root.

Same happens for categories.

⚠️ If you set this field to true for a category, all the posts linked to that category won't be generated as well for that language, no matter if you have translated them.

⚠️ I decided to not enforce field validation on slug field for Posts and Categories. Before publishing any content always check that such fields are populated for any locale you want to generate the page (unless you have set a fallback).


Editing the menu

Simply access the content model named Menu under the Data-only group:

Add a new "Menu Link" item and link the record you want to redirect the users. You can change the order of the block items by drag-and-drop and choose to set a complete different menu for each locale.


Displaying featured posts in the homepage

In each post record you see find this boolean field:

If set to true it will display that post in the homepage. You can select up to 6 posts, they will be ordered according to the update time.


Displaying the categories dropdown

For each menu item there's this boolean field:

If set to true it will render the categories dropdown in both desktop and mobile menus.

If you want to change the order, simply sort the records in the Categories content model via drap-and-drop:

⚠️ By default the dropdown will display the first six categories of your blog and a link at the bottom to redirect the user to the categories archive page. If you want to increase the number of categories shown, you can check this section.


Editing the categories menu

Each category page and the blog root page, displays a scrollable menu where you can navigate the categories:

If you want to change the order, simply rearrange the records order as explained in the previous section.

Once your categories number will increase and overflow the menu, a scrollbar with a gradient will appear:


Linking categories and authors to blog posts

Edit the category_link field in each post by selecting a category from the dropdown:

As soon as you add a new category it will be available for selection and your page paths generated accordingly.

You can also decide to not assign a category to a post, simply that post won't be rendered inside the category pages and the card won't display the category box:

The same happens for the authors, just add a new record in the Authors content model and update the record.

This is an example of uncategorized post.


Related posts

You can choose up to two posts to display below the article body:

All you have to do is to select the records in the link field of the Post content model.

If no posts are set, the section won't be displayed.


Styling

Colors

Navigate to src/components/Layout/sharedStyles/globalStyles.js and change the css variables values for the two following classes:

.lightTheme {
  --primaryColor: #0067fa;
  --headingsColor: #4d4d4d;
  --baseTextColor: #6e7581;
  --dividerColor: #e2e2e2;
  --markBackgroundColor: #fdffb4;
  --markTextColor: #4d4d4d;
  --inlineCodeTextColor: #4d4d4d;
  --inlineCodeBackgroundColor: #dbefff;
  --backgroundColor: #ffffff;
  --backgroundTransparentColor: rgba(255, 255, 255, 0);
  --backgroundColorAlt: #ffffff;
  --codeBlockBackgroundColor: #181b22;
}

.darkTheme {
  --primaryColor: #5995ea;
  --headingsColor: #eeeeee;
  --baseTextColor: #aaaaaa;
  --dividerColor: #242a31;
  --markBackgroundColor: #b2dbff;
  --markTextColor: #181b22;
  --inlineCodeTextColor: #ffffff;
  --inlineCodeBackgroundColor: #293b4a;
  --backgroundColor: #181b22;
  --backgroundTransparentColor: rgba(24, 27, 34, 0);
  --backgroundColorAlt: #1d2028;
  --codeBlockBackgroundColor: #181b22;
}

useTheme hook

If for any reason CSS variables are not enough to style both themes, you can import the useTheme hook:

import { useTheme } from 'src/hooks/useTheme';

And use it in any component / template:

const { isDark } = useTheme();

return <Header>{isDark ? <LogoBlack /> : <LogoWhite />}</Header>;

Logo

You can import and replace the logo in src/components/Layout/Header/Full.


Custom fonts

Please check this issue for a guide on how to change the default (safe) fonts.


Blog synthax highlighting

By using react-datocms' <StructuredText /> component, a custom render rule for the code node has been set using the amazing package react-synthax-hightlighter.

To change the theme, simply replace the import in src/templates/Article.jsx with your favourite style. Complete documentation and styles reference can be found here.

import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; // <-- Import your favourite style

If you want to override some styles from the original theme, you can do that by editing css rules in src/components/Layout/Blog/ArticleBody/styles.js.

If you wish to use another synthax highlighter, you can set your own renderNodeRule by following react-datocms documentation.


SEO

Access the Website Settings content model and set the Global SEO fields.

Global SEO 1

Once you have updated and localized the fields according to your content, you can edit any seo field of any content model of the Gatsby Node group.

A sitemap will automatically be generated on build time by gatsby-plugin-sitemap, all you need to do is to write your website address in the siteMetadata object in gatsby-config.js. The sitemap will be available at https://yourwebsite.com/sitemap/sitemap-0.xml


PWA

PWA support for any configured languages works out-of-the-box. All you need to do is to edit and localize the related settings fields in the content model named SEO and PWA:

PWA Settings

As soon as you will add a new language on Dato and localize its PWA settings, a new webmanifest file named manifest_<lang-code>.webmanifest will be exported to the /public folder and dynamically injected to the <head>. Instead, the default language webmanifest will always be named manifest.webmanifest.

PWA Icon Webmanifest files

Favicon will be generated automatically, based on the above icon. PWA meta tags are already inserted manually to the <Helmet /> component in /src/components/pageWrapper.jsx.

If you wish to customize the manifest JSON schema, you can edit the manifest object variables in gatsby-node.js, bear in mind that icons are downloaded in different sizes by using different imgixParams and aliases in the dedicated query in gatsby-node.js.


Redirect

When the user visits the homepage in default language ("/") a redirect will always take place if the language included in the Accept-Language request header matches one of the website's secondary languages.

The redirect will be performed server-side and not in the browser.

When starting the development server you will notice a warning like:

There are routes that match both page and redirect. Pages take precedence over redirects so the redirect will not work:

  • page: "/" and redirect: "/" -> "/it/"
  • page: "/" and redirect: "/" -> "/es-ES/"
  • page: "/" and redirect: "/" -> "/ar-AE/"

Do not worry, the redirect will take place as expected 😃

Please take note that the redirect will work only once the website has been deployed to Gatsby Cloud and not during local development.

If for any reason you want to disable the redirect you can remove the lines ~45 to 60 in gatsby-node.js.


404 page

Content for 404 page can be localized on DatoCMS in the content model named 404 Page.

Unfortunately, Gatsby doesn't allow to server-side render the 404 page on each request. Such page must be statically generated on each build.

However, content will always be rendered in correct language by following a different approach (in the browser):

Case 1 - Visiting a non-existent page for the very first time

Assuming that the user never visited the website and tries to access a non-existent page, we try to find a match among the website languages and the browser available languages.

Example:

  • Browser Languages: ["de-CH", "en-US"]

  • Website Languages: ["fr", "en", "de"]

In such case de is considered as the user's preferred language and they will see the correct localized content in German.

If there is no match, content in website default language will be rendered.

The preferred language is always evaluated according to the order priority of the browser (user) languages.

Case 2 - Visiting a non-existent page after having already browsed the website

When the user switches the language a new preferred_lang value is stored/overwritten in localStorage and it will be used to render the correspondent localized data for the 404 page.

If the user never switched the language, we assume their preferred language is the website default language and content will be rendered accordingly.


RTL text direction support

This starter has built-in support for any right-to-left language such as Hebrew or Arabic.

All you need to do is to add the language code to your DatoCMS languages list, set the fallbacks and start the dev server as explained above. If the language code matches one these lang codes, once you switch the language, the layout direction will change accordingly.

It doesn't matter if you choose "ar" or "ar-AE", the correct language direction will always be identified and injected to the <html> dir attribute.

useTextDirection hook

If for any reason you need to deeply style and change elements behavior according to the language direction, you can import the useTextDirection hook:

import { useTextDirection } from '../../hooks/useTextDirection';

And use it in any component / template:

const { isRtl } = useTextDirection();

return <span className={isRtl ? 'menuRight' : 'menuLeft'} />;

Extending the starter

If you are ok with the content structure of the starter you most likely won't need to extend it and follow those steps. In any case everything is explained below.

Removing fields

99% of the times that an issue occurs is because the fields or content models queried in the project files are not available anymore on DatoCMS. Gatsby can't find them and throws an error.

This can happen because:

  • You renamed the field ID
  • You deleted the field
  • You renamed the content model ID
  • You deleted the content model

Without deleting the field or renaming the field Id from the project files.


For example, if you don't want the author field of the content model blog_post to be displayed in your post pages:

1. Remove from any query the author field node:

export const query = graphql`
  query BlogPostTemplateQuery(
    $id: String!
    $locale: String!
  ) {
    datoCmsBlogPost(id: { eq: $id }, locale: { eq: $locale }) {
      title
      subtitle
      author { // <--- Delete from here
        authorName: name
        }
      } // <--- To here
      ...

2. Delete any destructured prop:

const BlogPostTemplate = ({
  data: {
    datoCmsBlogPost: {
      title,
      subtitle,
      author: { // <-- Delete from here
        authorName,
      }, // <-- to here
      ...

3. Then remove any expression/variable displaying the data you were querying:

  return (
    <Header>
      <AuthorDateContainer>
        <Author>{authorName}</Author> {/* <-- Delete this */}
        ...

By taking advantage of your code editor search you can easily check if the field is still queried or rendered anywhere.

4. Once done that, you can safely delete the field from the content model on DatoCMS.

⚠️ The only field you should never delete or rename the Id is the slug one. It is used to build the paths and by deleting/renaming it gatsby-node will throw an error. For any other field as long as you understood its purpose, you can safely get rid of it.


Removing pages and templates

If for example you want to remove any page included in the starter follow these steps:

  1. Identify the related content model on Dato and check where its fields are queried in the project files.

If the content model has this ID on Dato: other_page - The GraphQL fields to identify would be:

  • datoCmsOtherPage
  • allDatoCmsOtherPage

Hence the datoCms prefix and the snake_case converted to camelCase.

  1. With you code editor search for both field names among your project files and delete any prop destructuring, query or prop assignment.

  2. Access gatsby-node.js and delete the block containing the query, the loop and the template declaration. You will find them grouped and preceded by a comment.

  3. Delete the template used to generate such page.


Generating pages

If you wish to generate pages with your own path structure, the best thing you can do is to follow the comments in gatsby-node.js file.

The process is very quick and in short, all you have to do is to:

  • Create a content model on DatoCMS
    • Define and localize the slug field
    • Define and localze a seo field
    • Define and localize all content fields
  • Create a dummy JSX template in src/templates just to test page generation
  • Generate pages in gatsby-node.js using the dummy template and by querying the content model originalId and slug.
  • Once pages and paths are generated properly, edit and build the template by querying and filtering the correct data.

As soon as pages are generated correctly you can switch between languages as this feature is already set up.


Creating new templates

When creating new templates, always import and wrap your template around the PageWrapper component.

Pass to the pageData prop the entire pageContext object:

import { PageWrapper } from '../Layout/PageWrapper';

const BlogPostTemplate = ({ data, pageContext }) => (
  <PageWrapper pageData={pageContext}>
  ...

Then any child component will have access to the page locale exported to the pageContext object during build time by gatsby-node.js. This is required in order to render proper links in any Navigator component and in LanguageSwitcher as well.


Internal link navigation

Navigator component

When you need to render localized paths you have to use the built-in Navigator component. It's built on top of Gatsby Link and supports any prop you would normally add to it such as activeClassName.

There are only two props in addition:

Prop Description Required
recordId The originalId field value of the record used to generate the page Yes
passRef In case you need to pass a ref No

The component will take care of retrieving and render the proper path according to the page language.

Single link

Import the Navigator component in any of your template/component:

import { Navigator } from '../../../Navigator';

Query the originalId field of your record or content model (if a single instance) with GraphQL. You can use useStaticQuery as well:

export const query = graphql`
  query HomePageTemplate($locale: String!) {
    datoCmsBlogRoot(locale: { eq: $locale }) {
      id: originalId
      title
    }
    ...

Then pass the field value to the prop recordId:

<Navigator recordId={data.datoCmsBlogRoot.id}>
  {data.datoCmsBlogRoot.title}
</Navigator>

That's it. Now in any page the link is displayed with the proper path according to the page language.

Dynamic link with StructuredText

Since DatoCMS provides a set of components to work faster with React, it is possible to automate the rendering of any link or block included in a Structured Text Field with a custom component. The complete documentation can be found here.

This is so far the best approach because you will set the template only once, then your content editors will add new records and create links with no further intervention on your behalf.

Of course this is already set up for you in the starter templates but in case you want to create new ones, those are the steps you have to follow:

  1. Add a structured text field to your content model:

In the field settings specify from which content models the editors are allowed to add links:

Links will be available for selection to your content editors when writing posts:


  1. Query the structured text field:
export const query = graphql`
  query BlogPostTemplateQuery(
    $id: String!
    $locale: String!
  ) {
    datoCmsBlogPost(originalId: { eq: $id }, locale: { eq: $locale }) {
      structuredText {
        value
        links {
          ... on DatoCmsBlogPost {
            id: originalId
          }
          ... on DatoCmsOtherPage {
            id: originalId
          }
          ... on DatoCmsHomepage {
            id: originalId
          }
          ... on DatoCmsBlogRoot {
            id: originalId
          }
        }
        value
      }
      ...

⚠️ You will always have to query the originalId field with the alias id for each fragment representing the content models you allowed to be linked in the Structured text field:

  1. Import both StructuredText and Navigator in your template/component:
import { StructuredText } from 'react-datocms';
import { Navigator } from '../components/LanguageHelpers/Navigator';
  1. Use Navigator as renderer for the renderLinkToRecord prop and pass the id field to it:
const BlogPostTemplate = ({ data, pageContext }) => (
    <PageWrapper pageData={pageContext}>
        <ArticleBody>
          {data.datoCmsBlogPost.structuredText?.value && (
            <StructuredText
              key={id}
              data={structuredText}
              renderLinkToRecord={({
                record,
                children,
                transformedMeta,
              }) => (
                <Navigator {...transformedMeta} recordId={record.id}>
                  {children}
                </Navigator>
              )}
            />
          )}
        </ArticleBody>
...

Spread {...transformedMeta} to render any custom attribute you set on Dato when adding the link. Of course you can localize those attributes as long as the Structured Text field is set as localizable.


Template design strategy

Example case: You are going to create three pages with different layout and content: /contact, /about, /mission.

Typical approach

You create a single instance content model for each page with its own data fields or blocks, and then query, filter and render those fields in three different templates like ContactPage.jsx, AboutPage.jsx and Mission.jsx.

The template file will render the data of each single field of the content model:

const ContactPageTemplate = ({ data, pageContext }) => (
  <PageWrapper pageData={pageContext}>
    <Heading>{data.datoCmsContactPage.title}</Heading>
    <SubHeading>{data.datoCmsContactPage.subtitle}</SubHeading>
    <Hero
      title={data.datoCmsContactPage.hero.heroTitle}
      subtitle={data.datoCmsContactPage.hero.heroTitle}
    >
  </PageWrapper>
);

export default ContactPageTemplate;

export const query = graphql`
  query ContactPageQuery() {
    datoCmsContactPage(locale: $locale) {
      title
      subtitle
      hero {
        heroTitle
        heroSubtitle
      }
    }
  }
`;

Then you repeat those steps for any other page like /about and /mission.

So at the end of your journey you will have three different single-instance content models on Dato, three different templates in your project folder and three different queries for each template.

This approach has been used to generate the homepage, the categories archive page and the blog root page of this starter.

Structured text approach

Instead of creating a single-instance content model for each page, you can create one content model of type collection, add the mandatory shared fields such as seo and slug and then add a single Structured text field to hold all your content.

Then you can create one record for each page you want to generate and inside each Structured Text field add different blocks according to the page layout and content.

Once your records are ready with different content per Structured Text field you can set custom renderers inside the StructuredText component in a single shared template file.

At the end of this journey you will have, one template for all the pages you are going to generate, one query to retrieve all different data and one model of type collection.

This approach has been used to generate the /guide and /features pages of this starter by using the template OtherPages.jsx.

The best way to understand how it works is to read this article from the authors of DatoCMS and also to check the project files.

This opens the doors to different scenarios where for example, your content editors create pages, build the layout using a friendly UI completely without any developer intervention, just like they used to do with many Wordpress page builders.


Accessing languages

You most like won't need to but if you want to access the pageLocale, you can call this hook in any component file used in any of your templates:

import { usePageLocale } from '/src/hooks/usePageLocale';

And use it:

const { pageLocale } = usePageLocale();

console.log(pageLocale); // Logs "es-ES"

⚠️ Do not call this hook in your template files, call it in any component file and use it in your templates.

Same for the website languages:

import { useLocales } from '/src/hooks/useLocales';
const { locales, defaultLocale } = usedefaultLocale();

console.log(locales); // Logs ["en", "es", "it"]
console.log(defaultLocale); // Logs "en"

Formatting date and time

By default date strings coming from GraphQL queries:

query {
  datoCmsBlogPost {
    meta {
      firstPublishedAt // <-- Always returns "2021-07-19T12:12:15.597+02:00"
    }
  }
}

Are formatted according to the page language and the following options:

const timeOptions = {
  hour: '2-digit',
  minute: '2-digit',
};

// Time: 23:30 ("it") or 11:30 PM ("en")

const dateOptions = {
  year: 'numeric',
  month: 'short',
  day: 'numeric',
};

// Date: 23 Nov 2020 ("it") or Nov 23, 2020 ("en")

Options can be edited in src/functions/formatDateTime.js.

There are two functions in the above mentioned file:

formatDate(dateString, pageLocale) - Returns Jul 17, 2021 or 17 lug 2021 according to the page locale

formatDateTime(dateString, pageLocale) - Returns Feb 10, 2022, 12:20 PM or 10 feb 2022, 12:21

useFormattedDate hook

To quickly format the date coming from any DatoCMS / GraphQL field, import the hook:

import { useFormattedDate } from '../hooks/useFormattedDate';

And use it:

datoCmsBlogPost(originalId: { eq: $id }, locale: { eq: $locale }) {
  meta {
    updatedAt
  }
  ...

const { formattedDate } = useFormattedDate(data.datoCmsBlogPost.meta.updatedAt);

<time>{formattedDate}</time>

Increase categories number displayed by the dropdown

If you want to increase the number of categories displayed by the dropdown, you can edit the file src/components/Layout/Header/Full and increase the end parameter of the Array.prototype.slice() of the following variable:

const categoryNodesMatch = categoryNodes
  .filter(({ locale }) => locale === pageLocale)
  .slice(0, 6); // <-- Increase it

SEO

Each template must be wrapped in the component named <PageWrapper /> which accepts also three SEO-related props:

prop type description
seoTitle string Accepts the title defined in the seo field on any content model
seoDescription string Accepts the description defined in the seo field on any content model
seoImage string Accepts the image url uploaded in the seo field on any content model

If the fields return undefined or you don't set the props at all, global SEO fields will be rendered.

const HomePageTemplate = ({ data, pageContext }) => (
  <PageWrapper
    pageData={pageContext}
    seoTitle={data.datoCmsHomepage.seo?.title}
    seoDescription={data.datoCmsHomepage.seo?.description}
    seoImage={data.datoCmsHomepage.seo?.image?.url}
  >
  ...


export const query = graphql`
  query HomePageTemplate($locale: String!) {
    datoCmsHomepage(locale: { eq: $locale }) {
      seo {
        title
        description
        image {
          url
        }
      }
      ...

Troubleshooting

Navigator

This error will appear whenever the Navigator component is trying to render a link to a record which has never been generated as page by gatsby-node.js for that locale.

If the error for example appears in: localhost:8000/es/guia, there's a problem with Navigator component inside that page in Spanish.

This can happen for many different reasons:

General

  • You are passing the id field and not originalId (with the alias id).
  • You forgot to export the originalId field with the alias id to the pageContext object when generating pages.

Structured Text

  • You linked in a Structured Text field (on DatoCMS) a post/category which has been marked as non-translatable.
  • You linked in a Structured Text a post whose category has been marked as non-translatable.
  • You passed to the renderLinkToRecord StructuredText component prop the originalId of the StructuredText field itself.

Check the StructuredText field content on DatoCMS for that locale and make sure of it.

Also, always filter any post or category query, by excluding any record marked as non-translatable:

query {
  allDatoCmsCategory(filter: { noTranslate: { ne: true } }) {
    categoryNodes: nodes {
      id: originalId
    }
  }
}

GraphQL

Cannot query field "datoCmsContactPage" on type ...

You can encounter this error both during build time and development.

What happened here is that you removed a field or content model on DatoCMS and forgot to remove any query or variable referring to it in the project files. Please check this section on how to fix this issue.


Issues

If you encounter any different issue or you have any kind of request, please open an issue.

About

Spaces for news and relevant information about the foundation

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published