backstage icon indicating copy to clipboard operation
backstage copied to clipboard

[RFC] i18n feature

Open rbideau opened this issue 4 years ago • 7 comments
trafficstars

Status: Open for comments

Need

This issue is here to start the discussion about adding i18n support in backstage and see if there is enough interest for it.

Proposal

At first, the goal is to gather interest, collect feedback and then start fleshing out the requirement for an i18n feature.

If there is enough interest, we will need to :

  • evaluating potential react library (probably the core team is already more familiar with some)
  • deciding how to provide the translations in an app
  • ... ?

Alternatives

NA

Risks

  • Add complexity, in particular regarding plugin development
  • ... ?

rbideau avatar Feb 09 '21 09:02 rbideau

Just for awareness, Backstage's membership in CNCF may help: https://www.cncf.io/services-for-projects/#internationalization

Not suggesting this could "do the translation for us", but resources experienced in that area might have guidance/suggestions as well.

adamdmharvey avatar Feb 16 '21 05:02 adamdmharvey

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 17 '21 06:04 stale[bot]

Hey @freben, any news about that? I could help to make that happening

angeliski avatar Feb 23 '22 12:02 angeliski

No news yet. I can say though, that there's one more angle to this that makes i18n interesting: I'm getting more and more interest from people that want to replace individual little text snippets inside their Backstage instance, not for translation purposes, but for personalization / branding purposes. Sometimes we've added a prop or similar to make them customizable, but an i18n framework properly used will probably greatly help with things like that as well.

Any movement or suggestion is welcome. Maybe even starting with just pointing to commonly used libraries and their strengths/drawbacks

freben avatar Feb 24 '22 11:02 freben

@freben two that I would suggest is

https://www.npmjs.com/package/typesafe-i18n which looks super nice, but some like it is singularly maintained, and the use base is still growing

https://www.npmjs.com/package/i18next This one is a pretty industry standard. A lot of places use it and it has pretty wide range of documented integrations with external systems.

I see pluses and minuses in both directions. i18next just seems like a little better choice as a lot of adopters could leverage their existing tools and processes around localization.

webark avatar Sep 21 '22 04:09 webark

https://github.com/sibelius/ast-i18n

This also looks like an interesting project which could do a fair number of the initial heavy lifting maybe 🤷‍♂️ Might be worth a pass and a look?

webark avatar Sep 21 '22 05:09 webark

@webark yep looks interesting! At a first glance I'm a bit worried about seemingly generated message IDs, it'll make API stability a bit troublesome

Rugvip avatar Sep 22 '22 08:09 Rugvip

yea. I figured it would be interesting to see what it spit out and if it was helpful

webark avatar Sep 22 '22 14:09 webark

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Nov 21 '22 14:11 github-actions[bot]

We in the Backstage community in Brazil (in general) are very interested in this. 🇧🇷 We can even engage in this process.

padupe avatar Nov 21 '22 14:11 padupe

Status: Open for comments

My intention was to design a general ideia to provide a first step to use i18n in Backstage.

Need

Backstage is reaching a big audience and not everyone knows english, so the developers maintainers are in necessity for translate the product to provide a better experience.

Also some maintainers want to change some texts in the front-end but not every plugin (or component) has props for that

The ideia is add i18next and support a possibility to translate your plugin.

Proposal

The fist step is provide a simple integration for each plugin add tr

a simple draw from this simple relation

First we will update the PluginConfig to enable a new property locale

import {Resource} from "i18next";

export type PluginConfig<
  Routes extends AnyRoutes,
  ExternalRoutes extends AnyExternalRoutes,
  PluginInputOptions extends {},
> = {
  id: string;
  apis?: Iterable<AnyApiFactory>;
  routes?: Routes;
  externalRoutes?: ExternalRoutes;
  featureFlags?: PluginFeatureFlagConfig[];
  __experimentalConfigure?(options?: PluginInputOptions): {};
  locale?: Resource
};

This will provide a simple way from the plugin export the keys used inside ( we can provide definitions for that too).

Example:

export constcatalogPlugin= createPlugin({
  id: 'catalog',
  apis: [
    createApiFactory({
      api:catalogApiRef,
      deps: {
        discoveryApi:discoveryApiRef,
        fetchApi:fetchApiRef,
      },
      factory: ({ discoveryApi, fetchApi }) =>
        new CatalogClient({ discoveryApi, fetchApi }),
    }),
    createApiFactory({
      api:starredEntitiesApiRef,
      deps: { storageApi:storageApiRef},
      factory: ({ storageApi }) =>
        new DefaultStarredEntitiesApi({ storageApi }),
    }),
  ],
  routes: {
    catalogIndex:rootRouteRef,
    catalogEntity:entityRouteRef,
  },
  externalRoutes: {
    createComponent:createComponentRouteRef,
    viewTechDoc:viewTechDocRouteRef,
  },
  __experimentalConfigure(
    options?: CatalogInputPluginOptions,
  ): CatalogPluginOptions {
    const defaultOptions = {
      createButtonTitle: 'Create',
    };
    return { ...defaultOptions, ...options };
  },
  locale: {
    en: {
        name: "catalog",
        description: "All your software catalog entities"
    }
  }
});

And could be used like that in the component:

import React from 'react';
import { useTranslation } from 'react-i18next';

export function MyComponent() {
const { t } = useTranslation(["catalog"])

  return <p>{t('name')}</p>
}

important: Each component will be using a namespace based in the plugin id

open question: How provide a better devexp here? because in this way the developer needs to remember to use the namespace all the times

With this setup, the backstage could load locale too and override those keys

const app = createApp({
  localeConfig: {
    lng: "pt-br",
    supportedLngs: ['pt', 'en']
    resources: {
      pt: {
        app: {
          screeName: "Minha tela",
        },
        catalog: {
          name: "awesome catalog"
        }
      }
    }
  },
  apis,
  plugins: Object.values(plugins),
  icons: {
    // Custom icon example
    alert: AlarmIcon,
  },
  components: {
    SignInPage: props => {
      return (
        <SignInPage
          {...props}
          providers={['guest', 'custom', ...providers]}
          title="Select a sign-in method"
          align="center"
        />
      );
    },
  },
  bindRoutes({ bind }) {
    bind(catalogPlugin.externalRoutes, {
      createComponent: scaffolderPlugin.routes.root,
      viewTechDoc: techdocsPlugin.routes.docRoot,
    });
    bind(apiDocsPlugin.externalRoutes, {
      registerApi: catalogImportPlugin.routes.importPage,
    });
    bind(scaffolderPlugin.externalRoutes, {
      registerComponent: catalogImportPlugin.routes.importPage,
      viewTechDoc: techdocsPlugin.routes.docRoot,
    });
    bind(orgPlugin.externalRoutes, {
      catalogIndex: catalogPlugin.routes.catalogIndex,
    });
  },
});

Important The locale is following the hierarchy for keys:

language → plugin (namespace) → keys

You don’t need to provide all the keys, the core-app-api will mix those values with the plugin locale.

You can add new languages too to match your expectation over the changes in your app.

The keys you want to use in your app should be provided over the namespace app

localeConfig will accept almost the same configuration i18n.init to add more flexibility

Alternatives

Load from Backend

One interesting alternative is use the Backend system to load all translations. This could be nice to provide a simple way to change locales and a fast update system (like publish the core locales in a CDN)

I didn’t go deep in this approach but we can talk more about that

Use a differrent library

We can choose a different library (like https://github.com/ivanhofer/typesafe-i18n), but I think this will change only details about the implementation, not the whole approach (plugins are locale owner’ s).

My ideia is use i18next because is like a industry standard, so the commuinty don’t really need to learn a new library to start

Risks

How I know this plugin use i18n?

A pain point is how give this information for the developer. The experience could be weird when I have 10 plugins in my language and just one small part only in English.

We can enforce translation, but this could raise the contributing entrypoint

Static load

This solution is very simple and a start point. Because of that, the locale is almost all loadead when the AppManager.getProvider functions is called.

We have some options to add or load translations we could explore

Backend translation

This solution don’t have any use case to translate messages from the backend.

The experience could be weird when your interface is in a language and you receive some messages in English

Locales keys coupled with plugin release

In the current proposal, the keys values are really coupled with the plugin release.

In some cases, to add a new language for the plugin, I will need to release a new version every time.

We can isolate translations in a new module, something like this:

plugins/i18n-translations/src/catalog.ts

export default {
  en: {
    name: "catalog",
    description: "All your software catalog entities"
  },
  pt: {
    name: "catalog",
    description: "Todas as suas entidades de catálogo de software"
  }
}

And use in each plugin:

import { catalogLocale } from '@backstage/plugin-i18n-translations'

export const catalogPlugin = createPlugin({
  id: 'catalog',
	//	some code
  locale: catalogLocale
});

This approach could be nice to move all translations for one place, and we can add some utilities to help in the plugin development (Like a simple way to resolve the namespace for the plugin id)

Add complexity, in particular regarding plugin development

Using i18n is a new thing to be aware when creating a plugin. Over the time, this could bring a high entrypoint to new contributing to Backstage.

Over the time we can simplify this flow, using tools to give a better experience in this process

@Rugvip @freben please take a look in this first ideia, so we can move foward this RFC ;)

angeliski avatar Dec 02 '22 17:12 angeliski

Excellent proposal @angeliski!

padupe avatar Dec 05 '22 14:12 padupe

@angeliski awesome stuff indeed :grin: I think it's well worth moving forward. I think it'd be best if you move your proposal to a separate RFC tbh, and focus the discussion there on the implementation. That could be a draft PRFC too if it ends up making sense.

There are a couple of requirements that I'd like to add in to be considered as part of this:

Must haves

  • Dynamic loading of locales
    • Any messages loaded as part of the default locale of the plugins must be lazy loaded, preferably in parallel with the plugin itself.
    • Any additional locales and overrides must have the option to be lazy loaded.
  • Ability to override individual messages in the default locale
    • This is a very important use-case for this system, it's a frequent request to for example be able to change the messaging on a single button.

Nice to have

  • Type safety and docs for message declarations
    • It would be highly desirable to have the available messages be defined as types exported by the plugins, along with documentation that describes the usage of each message.
    • Tbh I almost consider this one a must have
  • Type safety when using messages in plugin.
    • If not already part of i18next we could consider adding a layer for this
  • A core locale for common messages.
    • This is to avoid duplication, for very common phrases like "Back", "Create", etc.
    • I'd say this is slightly lower prio because it's really only there to make supporting new languages easier, it doesn't really work well for the overrides use-case. I'm not sure we actually want this at all tbh.

Rugvip avatar Dec 08 '22 12:12 Rugvip

Nice @Rugvip Next week I will open another RFC to talk only about this implementation and to try to find solutions to those new points

angeliski avatar Dec 08 '22 17:12 angeliski

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Feb 06 '23 17:02 github-actions[bot]

@angeliski Do we had another RFC link. I want to know the newest solutions about i18n

mario-mui avatar Mar 13 '23 07:03 mario-mui

Hi! Just wanted to keep an eye on this RFC, also we did some first few steps to this direction in the Playlist plugin https://github.com/backstage/backstage/pull/16955

antoniobergas avatar Apr 05 '23 06:04 antoniobergas

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jun 04 '23 06:06 github-actions[bot]

Also interested in this; Canada has two official languages so using Backstage in gov would be improved with i18n.

This will also reach into the YAML for us since our services usually have two names and two acronyms. I'm just getting started with Backstage, but this is a quick draft of what I might go with

# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: SelfServicePortal
  labels:
    name-fr: Portail libre-service
    acronym-en: SSP
    acronym-fr: PLS
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: SelfServicePortal-DEV
  links:
    - title: Home page
      url: https://myhost/ssp-pls/en/
      icon: Home
      type: home-en
    - title: Page d'accueil
      url: https:/myhost/ssp-pls/fr/
      icon: Home
      type: home-fr
spec:
  type: website
  lifecycle: experimental
  owner: guests
  system: SelfServicePortal
  providesApis: [SSP-API]

Some thoughts:

  • Usually our stuff is named with both official languages at once "$en-$fr" => "SSP-PLS", but I'm inclined to store this information in a decomposed style
  • Name field constraints prevent the usage of diacritics, so having English name as the YAML name is probably best with using labels to store the other langs
  • metadata.title and metadata.description should probably also have i18n support
  • Search doesn't seem to consider labels by default, so searching by acronym or name-in-other-language doesn't work out of the box with this approach

noticing now that this approach will need some refinement since labels will also complain about diacritics if they don't even support spaces. Idk but it wouldn't surprise me if this was a k8s limitation as well. Maybe in kubernetes 2 we will finally be able to have emojis as our app selectors.

Policy check failed for system:default/selfserviceportal; caused by Error: "labels.name-fr" is not valid; expected a string that is sequences of [a-zA-Z0-9] separated by any of [-_.], at most 63 characters in total but found "Portail libre-service". To learn more about catalog file format, visit: https://github.com/backstage/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md

Personally, the only constraint on a text field should be the length. Any system that forces me to a-zA-Z0-9-_ would be better off using a GUID as the identifier, and a different field for display name. The docs mention that this is not cool since the GUID changes when the YAML is ingested, but that just seems like a tooling problem?

Maybe our editors should have a shortcut to generate a GUID instead of relying on auto generating one later. This could get messy with references between entities being a little more opaque, but editor hints are nothing new and could be used to give more context to GUIDs everywhere

image

Pretend that the comments were editor hints

apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  uid: a0c049cb-8a9d-4e1f-bb65-37092cacd100
  labels:
    name-en: Self Service Portal
    name-fr: Portail libre-service ééééééééééé
    acronym-en: SSP
    acronym-fr: PLS
spec:
  owner: group:175dc814-a284-423f-b2c1-9eb238369d91 # labels.name-en: Self Service Portal Owners
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  uid: 801347e3-ad45-41a4-bd9a-9e19bc90bbe3
  labels:
    name-en: SelfServicePortal-DEV
  links:
    - title: Home page
      url: https://myhost/ssp-pls/en/
      icon: Home
      type: home-en
    - title: Page d'accueil
      url: https://myhost/ssp-pls/fr/
      icon: Home
      type: home-fr
spec:
  type: website
  lifecycle: experimental
  owner: group:8865f809-021b-4fa6-bb36-c5d2048c4076 # labels.name-en: guests
  system: a0c049cb-8a9d-4e1f-bb65-37092cacd100 # labels.name-en: Self Service Portal
  providesApis:
    - 3e0523c7-e60c-41dd-8ba6-561f7679f579 # labels.name-en: Self Service Portal API

Intellisense could auto suggest the guid to tab-replace as you type the name for a thing you want to reference.

Backstage as a language server to provide the intellisense + cross-repo reference validation?

I think I've gotten off track, but hopefully this has been a helpful perspective.

TeamDman avatar Jul 19 '23 13:07 TeamDman

Canada has two official languages so using Backstage in gov would be improved with i18n

Actually, in some provinces (like Quebec) it would be a hard requirement to support both French and English.

pittar avatar Aug 04 '23 13:08 pittar

Another aspect relative to i18n is also the support for all kind of characters : this is currenlty not the case for tags. See #16278

Kris-B avatar Sep 11 '23 16:09 Kris-B

@Kris-B That's kinda different though. The catalog has adjustable validation rules internally where you control what characters are allowed to go into each field of an entity. So you should be able already to do what you need there, specifically in terms of getting those labels into the catalog storage. Then, as a separate concern, maybe you want to localize the presentation of those labels in the browser? That, indeed, could be targeted by this i18n feature.

freben avatar Sep 13 '23 09:09 freben

@rbideau any issues with this being closed now that the initial implementation (https://github.com/backstage/backstage/pull/17436) for this is in place and will be releases as part of the 1.18.0 release on September 19, 2023?

Many thanks to all those who helped on this, especially @mario-mui! 🚀

awanlin avatar Sep 15 '23 12:09 awanlin

Hi @awanlin, I don't use Backstage anymore but I'm glad the project is still active and that the i18n feature has come to fruition.

Feel free to close the issue 👍

rbideau avatar Sep 17 '23 09:09 rbideau

Yep this work is steaming ahead - closing this RFC!

freben avatar Sep 18 '23 09:09 freben