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

[RFC] i18n feature #4454

Closed
rbideau opened this issue Feb 9, 2021 · 25 comments
Closed

[RFC] i18n feature #4454

rbideau opened this issue Feb 9, 2021 · 25 comments
Assignees
Labels
rfc Request For Comment(s)

Comments

@rbideau
Copy link
Contributor

rbideau commented Feb 9, 2021

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 rbideau added the rfc Request For Comment(s) label Feb 9, 2021
@adamdmharvey
Copy link
Member

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.

@stale
Copy link

stale bot commented Apr 17, 2021

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 stale bot added the stale label Apr 17, 2021
@freben freben removed the stale label Apr 17, 2021
@angeliski
Copy link
Contributor

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

@freben
Copy link
Member

freben commented Feb 24, 2022

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

@webark
Copy link
Contributor

webark commented Sep 21, 2022

@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
Copy link
Contributor

webark commented Sep 21, 2022

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?

@Rugvip
Copy link
Member

Rugvip commented Sep 22, 2022

@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

@webark
Copy link
Contributor

webark commented Sep 22, 2022

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

@github-actions
Copy link
Contributor

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 github-actions bot added the stale label Nov 21, 2022
@padupe
Copy link
Contributor

padupe commented Nov 21, 2022

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

@github-actions github-actions bot removed the stale label Nov 21, 2022
@angeliski
Copy link
Contributor

angeliski commented Dec 2, 2022

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 ;)

@padupe
Copy link
Contributor

padupe commented Dec 5, 2022

Excellent proposal @angeliski!

@Rugvip
Copy link
Member

Rugvip commented Dec 8, 2022

@angeliski awesome stuff indeed 😁 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.

@angeliski
Copy link
Contributor

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

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2023

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 github-actions bot added the stale label Feb 6, 2023
@freben freben removed the stale label Feb 6, 2023
@mario-mui
Copy link
Contributor

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

@antoniobergas
Copy link
Contributor

Hi! Just wanted to keep an eye on this RFC, also we did some first few steps to this direction in the Playlist plugin
#16955

@github-actions
Copy link
Contributor

github-actions bot commented Jun 4, 2023

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.

@TeamDman
Copy link

TeamDman commented Jul 19, 2023

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.

@pittar
Copy link

pittar commented Aug 4, 2023

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.

@Kris-B
Copy link

Kris-B commented Sep 11, 2023

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

@freben
Copy link
Member

freben commented Sep 13, 2023

@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.

@awanlin
Copy link
Collaborator

awanlin commented Sep 15, 2023

@rbideau any issues with this being closed now that the initial implementation (#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! 🚀

@rbideau
Copy link
Contributor Author

rbideau commented Sep 17, 2023

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 👍

@freben
Copy link
Member

freben commented Sep 18, 2023

Yep this work is steaming ahead - closing this RFC!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc Request For Comment(s)
Projects
None yet
Development

No branches or pull requests