QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.25k stars 59 forks source link

Is there any ways to loop through a translation strings? #216

Closed cohapzp closed 11 months ago

cohapzp commented 11 months ago

Hello!

I am sorry if this topic was touched but I haven't found any solution in the docs and here. The issue is I want to iterate through translation object to render a list of strings under the certain object key.

Is it possible?

Thanks.

lindesvard commented 11 months ago

en/index.js

export default {
  dates: {
    monthsNames: {
      '0': 'January',
      '1': 'February',
      '2': 'March',
      '3': 'April',
      '4': 'May',
      '5': 'June',
      '6': 'July',
      '7': 'August',
      '8': 'September',
      '9': 'October',
      '10': 'November',
      '11': 'December',
    }
  }
}

Your component

function Comp() {
  const t = useT();
  const months = new Array(12)
    .fill(0)
    .map((_, i) => t(`dates.monthsNames.${i}` as 'dates.monthsNames.0'));

  // should print out January
  return <p>{months[0]}</p>
}
QuiiBz commented 11 months ago

@lindesvard's solution seems great. What's your use case?

cohapzp commented 11 months ago

@lindesvard's solution seems great. What's your use case?

Thanks @lindesvard , I will dig into that code, need to expand my knowledge a little bit to understand that.

@QuiiBz , what I want to achieve is to get data from fetch response(I can get three language variation from it), populate dedicated dictionaries and then use them in the app. I've managed to get, populate and use them, but in kinda limited way.

What I schematically got from response:

{
    "0": {
        "url": "/services",
        "title": "Services"
    },
    "1": {
        "url": "/aboutus",
        "title": "About us"
    },
    "2": {
        "url": "/contacts",
        "title": "Contacts"
    }
}

And how dictionary looks in this case:

import { getMenuItems } from "../../utilities/apiHelper";

const getEngMenu = await getMenuItems("/?lc=en");

export default {
  translations: {
    header: {
      menu: getEngMenu,
    },
  },
} as const;

So this is a menu which is should be looped through in order to show it on the page and I am trying to figure it out.

In the component I can access it via something like: {t("translations.header.menu.0.title")} Where the "title" goes from Type described for that kind of promise and because of that can be accessible to reference(no TypeScript error). But, obviously, hardcoded indexes are not the approach I am looking for. Sorry for maybe stupid explanations/questions and thank you for your time and response :)

QuiiBz commented 11 months ago

I'm not sure how we could have a better API for this. One thing that comes to my mind is that getMenuItems should be called outside of the locales file, which should only contain direct translations:

export default {
  translations: {
    header: {
      menu: {
        services: "Services",
        aboutus: "About us",
        contactus: "Contact us",
      },
    },
  },
} as const;

async function ServerComponent() {
  const menuItems = await getMenuItems(); // e.g. being an array of services, aboutus, contacts
  const t = await getI18n();

  menuItems.forEach(menuItem => {
    t(`translations.header.menu.${menuItem}`)
  });
}

However I don't know if that's something you can achieve easily in your application, but it makes more sense this way.

cohapzp commented 11 months ago

@QuiiBz works like a charm even with my data structure! But with few minor changes - .forEach returns void, so I've used .map instead:

{Object.keys(menuItemsArray).map(menuItem => {
        return <p>{t(`translations.header.menu.${menuItem}.title`)}</p>;
      })}

Plus TS shows warning expecting second parameter here but works as well: изображение

As for API - another package can output object instead of string using this kind of parameter: const translations = t("translations", { returnObjects: true }); --> work with "translations" like with object. Maybe it would be handy in these scenarios...

QuiiBz commented 11 months ago

Yeah the forEach was just an example. The TS error might be because the type of menuItem in the loop is string, instead of being narrowed down to 'services' | 'aboutus' | 'contactus'. You could manually cast it:

${menuItem as 'services' | 'aboutus' | 'contactus'}

As for the API, this seems a bit out-of-scope for next-international. It feels too specific to be included by default: most users won't need it, and you can "easily" achieve it yourself like you did. I'll close the issue for now but feel free to add more arguments if you believe it should be added.