@red-hat-developer-hub/backstage-plugin-translations 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-translations
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e515331: Serve the maintained JSON translations from the RHDH repository's `/translations` directory and merge them with mounted customer overrides in the NFS translation API. Expose the backend `i18n.overrides` configuration schema.
8
+
9
+ ## 0.5.1
10
+
11
+ ### Patch Changes
12
+
13
+ - f5f0d02: Updated translations for RHDH 2.1 (s3297).
14
+
3
15
  ## 0.5.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # translations
2
2
 
3
+ ## NFS JSON translations
4
+
5
+ Install `translationsApiModule` in the app's `features` list and install the
6
+ translations backend plugin. The module retains NFS `TranslationBlueprint`
7
+ resources, then merges the JSON catalog served by `/api/translations` over
8
+ each requested translation reference and locale. JSON files in RHDH's
9
+ repository-root `translations` directory, as well as files mounted there, are
10
+ discovered by the backend on startup;
11
+ `i18n.overrides` files take highest priority. Add a new language to the NFS
12
+ `app.extensions` language selector through `availableLanguages`.
13
+
14
+ ```ts
15
+ import translationsApiModule from '@red-hat-developer-hub/backstage-plugin-translations/translations-api-module';
16
+
17
+ const app = createApp({ features: [translationsApiModule] });
18
+ ```
19
+
3
20
  Welcome to the translations plugin!
4
21
 
5
22
  _This plugin was created through the Backstage CLI_
@@ -13,10 +13,15 @@ function removeNulls(messages) {
13
13
  );
14
14
  }
15
15
  class ResourceLoader {
16
- constructor(onLoad) {
16
+ constructor(onLoad, loadJsonTranslations, onJsonTranslationsLoaded = () => {
17
+ }) {
17
18
  this.onLoad = onLoad;
19
+ this.loadJsonTranslations = loadJsonTranslations;
20
+ this.onJsonTranslationsLoaded = onJsonTranslationsLoaded;
18
21
  }
19
22
  onLoad;
23
+ loadJsonTranslations;
24
+ onJsonTranslationsLoaded;
20
25
  /** Loaded resources by loader key */
21
26
  #loaded = /* @__PURE__ */ new Set();
22
27
  /** Resource loading promises by loader key */
@@ -37,11 +42,7 @@ class ResourceLoader {
37
42
  }
38
43
  needsLoading(language, namespace) {
39
44
  const key = this.#getLoaderKey(language, namespace);
40
- const loader = this.#loaders.get(key);
41
- if (!loader) {
42
- return false;
43
- }
44
- return !this.#loaded.has(key);
45
+ return this.#loaders.has(key) && !this.#loaded.has(key);
45
46
  }
46
47
  async load(language, namespace) {
47
48
  const key = this.#getLoaderKey(language, namespace);
@@ -57,10 +58,26 @@ class ResourceLoader {
57
58
  await loading;
58
59
  return;
59
60
  }
60
- const load = loader().then(
61
- (result) => {
62
- this.onLoad({ language, namespace, messages: result.messages });
61
+ const load = Promise.resolve().then(() => loader?.()).then(
62
+ (resource) => {
63
+ if (resource) {
64
+ this.onLoad({
65
+ language,
66
+ namespace,
67
+ messages: resource.messages
68
+ });
69
+ }
63
70
  this.#loaded.add(key);
71
+ if (this.loadJsonTranslations) {
72
+ void Promise.resolve().then(() => this.loadJsonTranslations?.()).then((jsonTranslations) => {
73
+ const overrides = jsonTranslations?.[namespace]?.[language];
74
+ if (overrides) {
75
+ this.onLoad({ language, namespace, messages: overrides });
76
+ this.onJsonTranslationsLoaded();
77
+ }
78
+ }).catch(() => {
79
+ });
80
+ }
64
81
  },
65
82
  (error) => {
66
83
  this.#loaded.add(key);
@@ -156,17 +173,23 @@ class I18nextTranslationApi {
156
173
  if (initialLanguage !== DEFAULT_LANGUAGE) {
157
174
  i18n.changeLanguage(initialLanguage);
158
175
  }
159
- const loader = new ResourceLoader((loaded) => {
160
- i18n.addResourceBundle(
161
- loaded.language,
162
- loaded.namespace,
163
- removeNulls(loaded.messages),
164
- false,
165
- // do not merge with existing translations
166
- true
167
- // overwrite translations
168
- );
169
- });
176
+ let notifyJsonTranslationsLoaded = () => {
177
+ };
178
+ const loader = new ResourceLoader(
179
+ (loaded) => {
180
+ i18n.addResourceBundle(
181
+ loaded.language,
182
+ loaded.namespace,
183
+ removeNulls(loaded.messages),
184
+ false,
185
+ // do not merge with existing translations
186
+ true
187
+ // incoming keys overwrite existing translations
188
+ );
189
+ },
190
+ options.loadJsonTranslations,
191
+ () => notifyJsonTranslationsLoaded()
192
+ );
170
193
  const resources = options?.resources || [];
171
194
  for (let i = resources.length - 1; i >= 0; i--) {
172
195
  const resource = resources[i];
@@ -190,6 +213,11 @@ class I18nextTranslationApi {
190
213
  options.languageApi.getLanguage().language,
191
214
  interpolator
192
215
  );
216
+ notifyJsonTranslationsLoaded = () => instance.#notifyTranslationListeners();
217
+ if (options.loadJsonTranslations) {
218
+ void options.loadJsonTranslations().then((translations) => instance.#applyJsonTranslations(translations)).catch(() => {
219
+ });
220
+ }
193
221
  options.languageApi.language$().subscribe(({ language }) => {
194
222
  instance.#changeLanguage(language);
195
223
  });
@@ -249,7 +277,10 @@ class I18nextTranslationApi {
249
277
  loadResource();
250
278
  }
251
279
  };
252
- if (this.#loader.needsLoading(this.#language, internalRef.id)) {
280
+ const initialSnapshot = this.#createSnapshot(internalRef);
281
+ if (initialSnapshot.ready) {
282
+ subscriber.next(initialSnapshot);
283
+ } else {
253
284
  loadResource();
254
285
  }
255
286
  this.#languageChangeListeners.add(onChange);
@@ -262,8 +293,27 @@ class I18nextTranslationApi {
262
293
  if (this.#language !== language) {
263
294
  this.#language = language;
264
295
  this.#i18n.changeLanguage(language);
265
- this.#languageChangeListeners.forEach((listener) => listener());
296
+ this.#notifyTranslationListeners();
297
+ }
298
+ }
299
+ #notifyTranslationListeners() {
300
+ this.#languageChangeListeners.forEach((listener) => listener());
301
+ }
302
+ #applyJsonTranslations(translations) {
303
+ for (const [namespace, languages] of Object.entries(translations)) {
304
+ for (const [language, messages] of Object.entries(languages)) {
305
+ this.#i18n.addResourceBundle(
306
+ language,
307
+ namespace,
308
+ messages,
309
+ false,
310
+ // shallowly merge flat messages, preserving unspecified keys
311
+ true
312
+ // incoming keys overwrite existing translations
313
+ );
314
+ }
266
315
  }
316
+ this.#notifyTranslationListeners();
267
317
  }
268
318
  #createSnapshot(internalRef) {
269
319
  if (this.#loader.needsLoading(this.#language, internalRef.id)) {
@@ -1 +1 @@
1
- {"version":3,"file":"I18nextTranslationApi.esm.js","sources":["../../src/apis/I18nextTranslationApi.ts"],"sourcesContent":["/*\n * Copyright The Backstage Authors and Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppLanguageApi,\n TranslationApi,\n TranslationFunction,\n TranslationMessages,\n TranslationRef,\n TranslationResource,\n TranslationSnapshot,\n} from '@backstage/core-plugin-api/alpha';\nimport {\n createInstance as createI18n,\n FormatFunction,\n Interpolator,\n TFunction,\n type i18n as I18n,\n} from 'i18next';\nimport ObservableImpl from 'zen-observable';\n\nimport { Observable } from '@backstage/types';\nimport { createElement, Fragment, ReactNode, isValidElement } from 'react';\nimport {\n InternalTranslationRef,\n toInternalTranslationRef,\n} from './TranslationRef';\nimport {\n InternalTranslationResourceLoader,\n toInternalTranslationResource,\n} from './TranslationResource';\n\nconst DEFAULT_LANGUAGE = 'en';\n\n/** @public */\nexport interface I18nextTranslationApiOptions {\n languageApi: AppLanguageApi;\n resources?: Array<TranslationMessages | TranslationResource>;\n}\n\nfunction removeNulls(\n messages: Record<string, string | null>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(messages).filter(\n (e): e is [string, string] => e[1] !== null,\n ),\n );\n}\n\n/**\n * The built-in i18next backend loading logic doesn't handle on the fly switches\n * of language very well. It gets a bit confused about whether resources are actually\n * loaded or not, so instead we implement our own resource loader.\n */\nclass ResourceLoader {\n /** Loaded resources by loader key */\n #loaded = new Set<string>();\n /** Resource loading promises by loader key */\n #loading = new Map<string, Promise<void>>();\n /** Loaders for each resource language */\n #loaders = new Map<string, InternalTranslationResourceLoader>();\n\n constructor(\n private readonly onLoad: (loaded: {\n language: string;\n namespace: string;\n messages: Record<string, string | null>;\n }) => void,\n ) {}\n\n addTranslationResource(resource: TranslationResource) {\n const internalResource = toInternalTranslationResource(resource);\n for (const entry of internalResource.resources) {\n const key = this.#getLoaderKey(entry.language, internalResource.id);\n\n // First loader to register wins, this means that resources registered in the app\n // have priority over default resource from translation refs\n if (!this.#loaders.has(key)) {\n this.#loaders.set(key, entry.loader);\n }\n }\n }\n\n #getLoaderKey(language: string, namespace: string) {\n return `${language}/${namespace}`;\n }\n\n needsLoading(language: string, namespace: string) {\n const key = this.#getLoaderKey(language, namespace);\n const loader = this.#loaders.get(key);\n if (!loader) {\n return false;\n }\n\n return !this.#loaded.has(key);\n }\n\n async load(language: string, namespace: string): Promise<void> {\n const key = this.#getLoaderKey(language, namespace);\n\n const loader = this.#loaders.get(key);\n if (!loader) {\n return;\n }\n\n if (this.#loaded.has(key)) {\n return;\n }\n\n const loading = this.#loading.get(key);\n if (loading) {\n await loading;\n return;\n }\n\n const load = loader().then(\n result => {\n this.onLoad({ language, namespace, messages: result.messages });\n this.#loaded.add(key);\n },\n error => {\n this.#loaded.add(key); // Do not try to load failed resources again\n throw error;\n },\n );\n this.#loading.set(key, load);\n await load;\n }\n}\n\n/**\n * A helper for implementing JSX interpolation\n */\nexport class JsxInterpolator {\n readonly #setFormatHook: (hook: FormatFunction) => void;\n readonly #marker: string;\n readonly #pattern: RegExp;\n\n static fromI18n(i18n: I18n) {\n const interpolator = i18n.services.interpolator as Interpolator & {\n format: FormatFunction;\n };\n const originalFormat = interpolator.format;\n\n let formatHook: FormatFunction | undefined;\n\n // This is the only way to override the format function of the interpolator\n // without overriding the default formatters. See the behavior here:\n // https://github.com/i18next/i18next/blob/c633121e57e2b6024080142d78027842bf2a6e5e/src/i18next.js#L120-L125\n interpolator.format = (value, format, lng, formatOpts) => {\n if (format) {\n return originalFormat(value, format, lng, formatOpts);\n }\n return formatHook?.(value, format, lng, formatOpts) ?? value;\n };\n\n return new JsxInterpolator(\n // Using a random marker to ensure it can't be misused\n Math.random().toString(36).substring(2, 8),\n hook => {\n formatHook = hook;\n },\n );\n }\n\n private constructor(\n marker: string,\n setFormatHook: (hook: FormatFunction) => void,\n ) {\n this.#setFormatHook = setFormatHook;\n this.#marker = marker;\n this.#pattern = new RegExp(`\\\\$${marker}\\\\(([^)]+)\\\\)`);\n }\n\n wrapT<TMessages extends { [key in string]: string }>(\n originalT: TFunction,\n ): TranslationFunction<TMessages> {\n return ((key, options) => {\n let elementsMap: Map<string, ReactNode> | undefined = undefined;\n\n // There's no way to override the format hook via the translation function\n // options, event though types indicate that it might be possible.\n // Instead, override the format function hook before every invocation and\n // rely on synchronous execution.\n this.#setFormatHook(value => {\n if (isValidElement(value)) {\n if (!elementsMap) {\n elementsMap = new Map();\n }\n const elementKey = elementsMap.size.toString();\n elementsMap.set(elementKey, value);\n\n return `$${this.#marker}(${elementKey})`;\n }\n return value;\n });\n\n // Overriding the return options is not allowed via TranslationFunction,\n // so this will always be a string\n const result = originalT(key, options as any) as unknown as string;\n if (!elementsMap) {\n return result;\n }\n\n const split = result.split(this.#pattern);\n\n return createElement(\n Fragment,\n null,\n ...split\n .map((part, index) => {\n if (index % 2 === 0) {\n return part;\n }\n return elementsMap?.get(part);\n })\n .filter(Boolean),\n );\n }) as TranslationFunction<TMessages>;\n }\n}\n\n/** @public */\nexport class I18nextTranslationApi implements TranslationApi {\n static create(options: I18nextTranslationApiOptions) {\n const { languages } = options.languageApi.getAvailableLanguages();\n\n const i18n = createI18n({\n fallbackLng: DEFAULT_LANGUAGE,\n supportedLngs: languages,\n interpolation: {\n escapeValue: false,\n // Used for the JsxInterpolator format hook\n alwaysFormat: true,\n },\n ns: [],\n defaultNS: false,\n fallbackNS: false,\n\n // Disable resource loading on init, meaning i18n will be ready to use immediately\n initImmediate: false,\n });\n\n i18n.init();\n if (!i18n.isInitialized) {\n throw new Error('i18next was unexpectedly not initialized');\n }\n\n const interpolator = JsxInterpolator.fromI18n(i18n);\n\n const { language: initialLanguage } = options.languageApi.getLanguage();\n if (initialLanguage !== DEFAULT_LANGUAGE) {\n i18n.changeLanguage(initialLanguage);\n }\n\n const loader = new ResourceLoader(loaded => {\n i18n.addResourceBundle(\n loaded.language,\n loaded.namespace,\n removeNulls(loaded.messages),\n false, // do not merge with existing translations\n true, // overwrite translations\n );\n });\n\n const resources = options?.resources || [];\n // Iterate in reverse, giving higher priority to resources registered later\n for (let i = resources.length - 1; i >= 0; i--) {\n const resource = resources[i];\n if (resource.$$type === '@backstage/TranslationResource') {\n loader.addTranslationResource(resource);\n } else if (resource.$$type === '@backstage/TranslationMessages') {\n // Overrides for default messages, created with createTranslationMessages and installed via app\n i18n.addResourceBundle(\n DEFAULT_LANGUAGE,\n resource.id,\n removeNulls(resource.messages),\n true, // merge with existing translations\n false, // do not overwrite translations\n );\n }\n }\n\n const instance = new I18nextTranslationApi(\n i18n,\n loader,\n options.languageApi.getLanguage().language,\n interpolator,\n );\n\n options.languageApi.language$().subscribe(({ language }) => {\n instance.#changeLanguage(language);\n });\n\n return instance;\n }\n\n #i18n: I18n;\n #loader: ResourceLoader;\n #language: string;\n #jsxInterpolator: JsxInterpolator;\n\n /** Keep track of which refs we have registered default resources for */\n #registeredRefs = new Set<string>();\n /** Notify observers when language changes */\n #languageChangeListeners = new Set<() => void>();\n\n private constructor(\n i18n: I18n,\n loader: ResourceLoader,\n language: string,\n jsxInterpolator: JsxInterpolator,\n ) {\n this.#i18n = i18n;\n this.#loader = loader;\n this.#language = language;\n this.#jsxInterpolator = jsxInterpolator;\n }\n\n getI18nInstance(): I18n {\n return this.#i18n;\n }\n\n getTranslation<TMessages extends { [key in string]: string }>(\n translationRef: TranslationRef<string, TMessages>,\n ): TranslationSnapshot<TMessages> {\n const internalRef = toInternalTranslationRef(translationRef);\n\n this.#registerDefaults(internalRef);\n\n return this.#createSnapshot(internalRef);\n }\n\n translation$<TMessages extends { [key in string]: string }>(\n translationRef: TranslationRef<string, TMessages>,\n ): Observable<TranslationSnapshot<TMessages>> {\n const internalRef = toInternalTranslationRef(translationRef);\n\n this.#registerDefaults(internalRef);\n\n return new ObservableImpl<TranslationSnapshot<TMessages>>(subscriber => {\n let loadTicket = {}; // To check for stale loads\n\n const loadResource = () => {\n loadTicket = {};\n const ticket = loadTicket;\n this.#loader.load(this.#language, internalRef.id).then(\n () => {\n if (ticket === loadTicket) {\n const snapshot = this.#createSnapshot(internalRef);\n if (snapshot.ready) {\n subscriber.next(snapshot);\n }\n }\n },\n error => {\n if (ticket === loadTicket) {\n subscriber.error(Array.isArray(error) ? error[0] : error);\n }\n },\n );\n };\n\n const onChange = () => {\n const snapshot = this.#createSnapshot(internalRef);\n if (snapshot.ready) {\n subscriber.next(snapshot);\n } else {\n loadResource();\n }\n };\n\n if (this.#loader.needsLoading(this.#language, internalRef.id)) {\n loadResource();\n }\n\n this.#languageChangeListeners.add(onChange);\n return () => {\n this.#languageChangeListeners.delete(onChange);\n };\n });\n }\n\n #changeLanguage(language: string): void {\n if (this.#language !== language) {\n this.#language = language;\n this.#i18n.changeLanguage(language);\n this.#languageChangeListeners.forEach(listener => listener());\n }\n }\n\n #createSnapshot<TMessages extends { [key in string]: string }>(\n internalRef: InternalTranslationRef<string, TMessages>,\n ): TranslationSnapshot<TMessages> {\n if (this.#loader.needsLoading(this.#language, internalRef.id)) {\n return { ready: false };\n }\n\n const unwrappedT = this.#i18n.getFixedT(null, internalRef.id);\n const t = this.#jsxInterpolator.wrapT<TMessages>(unwrappedT);\n\n return {\n ready: true,\n t,\n };\n }\n\n #registerDefaults(internalRef: InternalTranslationRef): void {\n if (this.#registeredRefs.has(internalRef.id)) {\n return;\n }\n this.#registeredRefs.add(internalRef.id);\n\n const defaultMessages = internalRef.getDefaultMessages();\n this.#i18n.addResourceBundle(\n DEFAULT_LANGUAGE,\n internalRef.id,\n defaultMessages,\n true, // merge with existing translations\n false, // do not overwrite translations\n );\n\n const defaultResource = internalRef.getDefaultResource();\n if (defaultResource) {\n this.#loader.addTranslationResource(defaultResource);\n }\n }\n}\n"],"names":["createI18n"],"mappings":"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"}
1
+ {"version":3,"file":"I18nextTranslationApi.esm.js","sources":["../../src/apis/I18nextTranslationApi.ts"],"sourcesContent":["/*\n * Copyright The Backstage Authors and Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppLanguageApi,\n TranslationApi,\n TranslationFunction,\n TranslationMessages,\n TranslationRef,\n TranslationResource,\n TranslationSnapshot,\n} from '@backstage/core-plugin-api/alpha';\nimport {\n createInstance as createI18n,\n FormatFunction,\n Interpolator,\n TFunction,\n type i18n as I18n,\n} from 'i18next';\nimport ObservableImpl from 'zen-observable';\n\nimport { Observable } from '@backstage/types';\nimport { createElement, Fragment, ReactNode, isValidElement } from 'react';\nimport {\n InternalTranslationRef,\n toInternalTranslationRef,\n} from './TranslationRef';\nimport {\n InternalTranslationResourceLoader,\n toInternalTranslationResource,\n} from './TranslationResource';\n\nconst DEFAULT_LANGUAGE = 'en';\n\n/** @public */\nexport interface I18nextTranslationApiOptions {\n languageApi: AppLanguageApi;\n resources?: Array<TranslationMessages | TranslationResource>;\n /** JSON catalog and pod-mounted overrides served by translations-backend. */\n loadJsonTranslations?: () => Promise<\n Record<string, Record<string, Record<string, string>>>\n >;\n}\n\nfunction removeNulls(\n messages: Record<string, string | null>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(messages).filter(\n (e): e is [string, string] => e[1] !== null,\n ),\n );\n}\n\n/**\n * The built-in i18next backend loading logic doesn't handle on the fly switches\n * of language very well. It gets a bit confused about whether resources are actually\n * loaded or not, so instead we implement our own resource loader.\n */\nclass ResourceLoader {\n /** Loaded resources by loader key */\n #loaded = new Set<string>();\n /** Resource loading promises by loader key */\n #loading = new Map<string, Promise<void>>();\n /** Loaders for each resource language */\n #loaders = new Map<string, InternalTranslationResourceLoader>();\n\n constructor(\n private readonly onLoad: (loaded: {\n language: string;\n namespace: string;\n messages: Record<string, string | null>;\n }) => void,\n private readonly loadJsonTranslations?: I18nextTranslationApiOptions['loadJsonTranslations'],\n private readonly onJsonTranslationsLoaded: () => void = () => {},\n ) {}\n\n addTranslationResource(resource: TranslationResource) {\n const internalResource = toInternalTranslationResource(resource);\n for (const entry of internalResource.resources) {\n const key = this.#getLoaderKey(entry.language, internalResource.id);\n\n // First loader to register wins, this means that resources registered in the app\n // have priority over default resource from translation refs\n if (!this.#loaders.has(key)) {\n this.#loaders.set(key, entry.loader);\n }\n }\n }\n\n #getLoaderKey(language: string, namespace: string) {\n return `${language}/${namespace}`;\n }\n\n needsLoading(language: string, namespace: string) {\n const key = this.#getLoaderKey(language, namespace);\n return this.#loaders.has(key) && !this.#loaded.has(key);\n }\n\n async load(language: string, namespace: string): Promise<void> {\n const key = this.#getLoaderKey(language, namespace);\n\n const loader = this.#loaders.get(key);\n if (!loader) {\n return;\n }\n\n if (this.#loaded.has(key)) {\n return;\n }\n\n const loading = this.#loading.get(key);\n if (loading) {\n await loading;\n return;\n }\n\n const load = Promise.resolve()\n .then(() => loader?.())\n .then(\n resource => {\n if (resource) {\n this.onLoad({\n language,\n namespace,\n messages: resource.messages,\n });\n }\n this.#loaded.add(key);\n\n if (this.loadJsonTranslations) {\n void Promise.resolve()\n .then(() => this.loadJsonTranslations?.())\n .then(jsonTranslations => {\n const overrides = jsonTranslations?.[namespace]?.[language];\n if (overrides) {\n this.onLoad({ language, namespace, messages: overrides });\n this.onJsonTranslationsLoaded();\n }\n })\n .catch(() => {\n // JSON loading is optional; keep local translations available.\n });\n }\n },\n error => {\n this.#loaded.add(key); // Do not try to load failed resources again\n throw error;\n },\n );\n this.#loading.set(key, load);\n await load;\n }\n}\n\n/**\n * A helper for implementing JSX interpolation\n */\nexport class JsxInterpolator {\n readonly #setFormatHook: (hook: FormatFunction) => void;\n readonly #marker: string;\n readonly #pattern: RegExp;\n\n static fromI18n(i18n: I18n) {\n const interpolator = i18n.services.interpolator as Interpolator & {\n format: FormatFunction;\n };\n const originalFormat = interpolator.format;\n\n let formatHook: FormatFunction | undefined;\n\n // This is the only way to override the format function of the interpolator\n // without overriding the default formatters. See the behavior here:\n // https://github.com/i18next/i18next/blob/c633121e57e2b6024080142d78027842bf2a6e5e/src/i18next.js#L120-L125\n interpolator.format = (value, format, lng, formatOpts) => {\n if (format) {\n return originalFormat(value, format, lng, formatOpts);\n }\n return formatHook?.(value, format, lng, formatOpts) ?? value;\n };\n\n return new JsxInterpolator(\n // Using a random marker to ensure it can't be misused\n Math.random().toString(36).substring(2, 8),\n hook => {\n formatHook = hook;\n },\n );\n }\n\n private constructor(\n marker: string,\n setFormatHook: (hook: FormatFunction) => void,\n ) {\n this.#setFormatHook = setFormatHook;\n this.#marker = marker;\n this.#pattern = new RegExp(`\\\\$${marker}\\\\(([^)]+)\\\\)`);\n }\n\n wrapT<TMessages extends { [key in string]: string }>(\n originalT: TFunction,\n ): TranslationFunction<TMessages> {\n return ((key, options) => {\n let elementsMap: Map<string, ReactNode> | undefined = undefined;\n\n // There's no way to override the format hook via the translation function\n // options, event though types indicate that it might be possible.\n // Instead, override the format function hook before every invocation and\n // rely on synchronous execution.\n this.#setFormatHook(value => {\n if (isValidElement(value)) {\n if (!elementsMap) {\n elementsMap = new Map();\n }\n const elementKey = elementsMap.size.toString();\n elementsMap.set(elementKey, value);\n\n return `$${this.#marker}(${elementKey})`;\n }\n return value;\n });\n\n // Overriding the return options is not allowed via TranslationFunction,\n // so this will always be a string\n const result = originalT(key, options as any) as unknown as string;\n if (!elementsMap) {\n return result;\n }\n\n const split = result.split(this.#pattern);\n\n return createElement(\n Fragment,\n null,\n ...split\n .map((part, index) => {\n if (index % 2 === 0) {\n return part;\n }\n return elementsMap?.get(part);\n })\n .filter(Boolean),\n );\n }) as TranslationFunction<TMessages>;\n }\n}\n\n/** @public */\nexport class I18nextTranslationApi implements TranslationApi {\n static create(options: I18nextTranslationApiOptions) {\n const { languages } = options.languageApi.getAvailableLanguages();\n\n const i18n = createI18n({\n fallbackLng: DEFAULT_LANGUAGE,\n supportedLngs: languages,\n interpolation: {\n escapeValue: false,\n // Used for the JsxInterpolator format hook\n alwaysFormat: true,\n },\n ns: [],\n defaultNS: false,\n fallbackNS: false,\n\n // Disable resource loading on init, meaning i18n will be ready to use immediately\n initImmediate: false,\n });\n\n i18n.init();\n if (!i18n.isInitialized) {\n throw new Error('i18next was unexpectedly not initialized');\n }\n\n const interpolator = JsxInterpolator.fromI18n(i18n);\n\n const { language: initialLanguage } = options.languageApi.getLanguage();\n if (initialLanguage !== DEFAULT_LANGUAGE) {\n i18n.changeLanguage(initialLanguage);\n }\n\n let notifyJsonTranslationsLoaded = () => {};\n const loader = new ResourceLoader(\n loaded => {\n i18n.addResourceBundle(\n loaded.language,\n loaded.namespace,\n removeNulls(loaded.messages),\n false, // do not merge with existing translations\n true, // incoming keys overwrite existing translations\n );\n },\n options.loadJsonTranslations,\n () => notifyJsonTranslationsLoaded(),\n );\n\n const resources = options?.resources || [];\n // Iterate in reverse, giving higher priority to resources registered later\n for (let i = resources.length - 1; i >= 0; i--) {\n const resource = resources[i];\n if (resource.$$type === '@backstage/TranslationResource') {\n loader.addTranslationResource(resource);\n } else if (resource.$$type === '@backstage/TranslationMessages') {\n // Overrides for default messages, created with createTranslationMessages and installed via app\n i18n.addResourceBundle(\n DEFAULT_LANGUAGE,\n resource.id,\n removeNulls(resource.messages),\n true, // merge with existing translations\n false, // do not overwrite translations\n );\n }\n }\n\n const instance = new I18nextTranslationApi(\n i18n,\n loader,\n options.languageApi.getLanguage().language,\n interpolator,\n );\n notifyJsonTranslationsLoaded = () => instance.#notifyTranslationListeners();\n\n if (options.loadJsonTranslations) {\n void options\n .loadJsonTranslations()\n .then(translations => instance.#applyJsonTranslations(translations))\n .catch(() => {\n // JSON loading is optional; keep local translations available.\n });\n }\n\n options.languageApi.language$().subscribe(({ language }) => {\n instance.#changeLanguage(language);\n });\n\n return instance;\n }\n\n #i18n: I18n;\n #loader: ResourceLoader;\n #language: string;\n #jsxInterpolator: JsxInterpolator;\n\n /** Keep track of which refs we have registered default resources for */\n #registeredRefs = new Set<string>();\n /** Notify observers when language changes */\n #languageChangeListeners = new Set<() => void>();\n\n private constructor(\n i18n: I18n,\n loader: ResourceLoader,\n language: string,\n jsxInterpolator: JsxInterpolator,\n ) {\n this.#i18n = i18n;\n this.#loader = loader;\n this.#language = language;\n this.#jsxInterpolator = jsxInterpolator;\n }\n\n getI18nInstance(): I18n {\n return this.#i18n;\n }\n\n getTranslation<TMessages extends { [key in string]: string }>(\n translationRef: TranslationRef<string, TMessages>,\n ): TranslationSnapshot<TMessages> {\n const internalRef = toInternalTranslationRef(translationRef);\n\n this.#registerDefaults(internalRef);\n\n return this.#createSnapshot(internalRef);\n }\n\n translation$<TMessages extends { [key in string]: string }>(\n translationRef: TranslationRef<string, TMessages>,\n ): Observable<TranslationSnapshot<TMessages>> {\n const internalRef = toInternalTranslationRef(translationRef);\n\n this.#registerDefaults(internalRef);\n\n return new ObservableImpl<TranslationSnapshot<TMessages>>(subscriber => {\n let loadTicket = {}; // To check for stale loads\n\n const loadResource = () => {\n loadTicket = {};\n const ticket = loadTicket;\n this.#loader.load(this.#language, internalRef.id).then(\n () => {\n if (ticket === loadTicket) {\n const snapshot = this.#createSnapshot(internalRef);\n if (snapshot.ready) {\n subscriber.next(snapshot);\n }\n }\n },\n error => {\n if (ticket === loadTicket) {\n subscriber.error(Array.isArray(error) ? error[0] : error);\n }\n },\n );\n };\n\n const onChange = () => {\n const snapshot = this.#createSnapshot(internalRef);\n if (snapshot.ready) {\n subscriber.next(snapshot);\n } else {\n loadResource();\n }\n };\n\n const initialSnapshot = this.#createSnapshot(internalRef);\n if (initialSnapshot.ready) {\n subscriber.next(initialSnapshot);\n } else {\n loadResource();\n }\n\n this.#languageChangeListeners.add(onChange);\n return () => {\n this.#languageChangeListeners.delete(onChange);\n };\n });\n }\n\n #changeLanguage(language: string): void {\n if (this.#language !== language) {\n this.#language = language;\n this.#i18n.changeLanguage(language);\n this.#notifyTranslationListeners();\n }\n }\n\n #notifyTranslationListeners(): void {\n this.#languageChangeListeners.forEach(listener => listener());\n }\n\n #applyJsonTranslations(\n translations: Record<string, Record<string, Record<string, string>>>,\n ): void {\n for (const [namespace, languages] of Object.entries(translations)) {\n for (const [language, messages] of Object.entries(languages)) {\n this.#i18n.addResourceBundle(\n language,\n namespace,\n messages,\n false, // shallowly merge flat messages, preserving unspecified keys\n true, // incoming keys overwrite existing translations\n );\n }\n }\n this.#notifyTranslationListeners();\n }\n\n #createSnapshot<TMessages extends { [key in string]: string }>(\n internalRef: InternalTranslationRef<string, TMessages>,\n ): TranslationSnapshot<TMessages> {\n if (this.#loader.needsLoading(this.#language, internalRef.id)) {\n return { ready: false };\n }\n\n const unwrappedT = this.#i18n.getFixedT(null, internalRef.id);\n const t = this.#jsxInterpolator.wrapT<TMessages>(unwrappedT);\n\n return {\n ready: true,\n t,\n };\n }\n\n #registerDefaults(internalRef: InternalTranslationRef): void {\n if (this.#registeredRefs.has(internalRef.id)) {\n return;\n }\n this.#registeredRefs.add(internalRef.id);\n\n const defaultMessages = internalRef.getDefaultMessages();\n this.#i18n.addResourceBundle(\n DEFAULT_LANGUAGE,\n internalRef.id,\n defaultMessages,\n true, // merge with existing translations\n false, // do not overwrite translations\n );\n\n const defaultResource = internalRef.getDefaultResource();\n if (defaultResource) {\n this.#loader.addTranslationResource(defaultResource);\n }\n }\n}\n"],"names":["createI18n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ function createJsonTranslationsLoader(request, onError) {
2
+ let cachedTranslations;
3
+ let loading;
4
+ return () => {
5
+ if (cachedTranslations) {
6
+ return Promise.resolve(cachedTranslations);
7
+ }
8
+ if (loading) {
9
+ return loading;
10
+ }
11
+ const currentLoading = request().catch(() => request()).then((translations) => {
12
+ cachedTranslations = translations;
13
+ return translations;
14
+ }).catch((error) => {
15
+ onError(error);
16
+ return {};
17
+ }).finally(() => {
18
+ loading = void 0;
19
+ });
20
+ loading = currentLoading;
21
+ return currentLoading;
22
+ };
23
+ }
24
+
25
+ export { createJsonTranslationsLoader };
26
+ //# sourceMappingURL=jsonTranslations.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonTranslations.esm.js","sources":["../../src/apis/jsonTranslations.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** @internal */\nexport type JsonTranslations = Record<\n string,\n Record<string, Record<string, string>>\n>;\n\n/** @internal */\nexport function createJsonTranslationsLoader(\n request: () => Promise<JsonTranslations>,\n onError: (error: unknown) => void,\n): () => Promise<JsonTranslations> {\n let cachedTranslations: JsonTranslations | undefined;\n let loading: Promise<JsonTranslations> | undefined;\n\n return () => {\n if (cachedTranslations) {\n return Promise.resolve(cachedTranslations);\n }\n if (loading) {\n return loading;\n }\n\n const currentLoading = request()\n .catch(() => request())\n .then(translations => {\n cachedTranslations = translations;\n return translations;\n })\n .catch(error => {\n onError(error);\n return {};\n })\n .finally(() => {\n loading = undefined;\n });\n\n loading = currentLoading;\n return currentLoading;\n };\n}\n"],"names":[],"mappings":"AAuBO,SAAS,4BAAA,CACd,SACA,OAAA,EACiC;AACjC,EAAA,IAAI,kBAAA;AACJ,EAAA,IAAI,OAAA;AAEJ,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,kBAAA,EAAoB;AACtB,MAAA,OAAO,OAAA,CAAQ,QAAQ,kBAAkB,CAAA;AAAA,IAC3C;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAO,OAAA;AAAA,IACT;AAEA,IAAA,MAAM,cAAA,GAAiB,SAAQ,CAC5B,KAAA,CAAM,MAAM,OAAA,EAAS,CAAA,CACrB,IAAA,CAAK,CAAA,YAAA,KAAgB;AACpB,MAAA,kBAAA,GAAqB,YAAA;AACrB,MAAA,OAAO,YAAA;AAAA,IACT,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,KAAA,KAAS;AACd,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA,OAAO,EAAC;AAAA,IACV,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,OAAA,GAAU,MAAA;AAAA,IACZ,CAAC,CAAA;AAEH,IAAA,OAAA,GAAU,cAAA;AACV,IAAA,OAAO,cAAA;AAAA,EACT,CAAA;AACF;;;;"}
package/dist/index.esm.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { PageBlueprint, ApiBlueprint, configApiRef, appLanguageApiRef, translationApiRef, createExtensionInput, createFrontendPlugin, createFrontendModule } from '@backstage/frontend-plugin-api';
2
+ import { PageBlueprint, ApiBlueprint, fetchApiRef, configApiRef, appLanguageApiRef, translationApiRef, createExtensionInput, createFrontendPlugin, createFrontendModule } from '@backstage/frontend-plugin-api';
3
+ import { discoveryApiRef } from '@backstage/core-plugin-api';
3
4
  import ExtensionIcon from '@mui/icons-material/Extension';
4
5
  import { TranslationBlueprint, AppRootWrapperBlueprint } from '@backstage/plugin-app-react';
5
6
  import { translationsPluginTranslations } from './translations/index.esm.js';
6
7
  import { I18nextTranslationApi } from './apis/I18nextTranslationApi.esm.js';
8
+ import { createJsonTranslationsLoader } from './apis/jsonTranslations.esm.js';
7
9
  import { attachPseudolocalizationIfEnabled } from './apis/pseudolocalization.esm.js';
8
10
  import { PseudoLocalizationProvider } from './components/PseudoLocalizationProvider.esm.js';
9
11
  export { translationsPluginTranslationRef } from './translations/ref.esm.js';
10
12
 
13
+ const JSON_TRANSLATIONS_TIMEOUT_MS = 1e4;
11
14
  const translationsPage = PageBlueprint.make({
12
15
  params: {
13
16
  path: "/translations",
@@ -35,14 +38,55 @@ const translationsApiExtension = ApiBlueprint.makeWithOverrides({
35
38
  return originalFactory(
36
39
  (defineParams) => defineParams({
37
40
  api: translationApiRef,
38
- deps: { languageApi: appLanguageApiRef, configApi: configApiRef },
39
- factory: ({ languageApi, configApi }) => {
41
+ deps: {
42
+ languageApi: appLanguageApiRef,
43
+ configApi: configApiRef,
44
+ discoveryApi: discoveryApiRef,
45
+ fetchApi: fetchApiRef
46
+ },
47
+ factory: ({ languageApi, configApi, discoveryApi, fetchApi }) => {
40
48
  const resources = inputs.translations.map(
41
49
  (i) => i.get(TranslationBlueprint.dataRefs.translation)
42
50
  );
51
+ const requestJsonTranslations = async () => {
52
+ const controller = new AbortController();
53
+ let timeoutId;
54
+ const timeout = new Promise((_, reject) => {
55
+ timeoutId = setTimeout(() => {
56
+ controller.abort();
57
+ reject(
58
+ new Error(
59
+ `Timed out after ${JSON_TRANSLATIONS_TIMEOUT_MS}ms`
60
+ )
61
+ );
62
+ }, JSON_TRANSLATIONS_TIMEOUT_MS);
63
+ });
64
+ const request = discoveryApi.getBaseUrl("translations").then(
65
+ (baseUrl) => fetchApi.fetch(baseUrl, { signal: controller.signal })
66
+ ).then((response) => {
67
+ if (!response.ok) {
68
+ throw new Error(`HTTP ${response.status}`);
69
+ }
70
+ return response.json();
71
+ });
72
+ try {
73
+ return await Promise.race([request, timeout]);
74
+ } finally {
75
+ if (timeoutId) {
76
+ clearTimeout(timeoutId);
77
+ }
78
+ }
79
+ };
80
+ const loadJsonTranslations = createJsonTranslationsLoader(
81
+ requestJsonTranslations,
82
+ (error) => {
83
+ console.warn(`Unable to load JSON translations: ${error}`);
84
+ }
85
+ );
43
86
  const api = I18nextTranslationApi.create({
44
87
  languageApi,
45
- resources
88
+ resources,
89
+ loadJsonTranslations
46
90
  });
47
91
  attachPseudolocalizationIfEnabled(api, configApi);
48
92
  return api;
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/index.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ApiBlueprint,\n appLanguageApiRef,\n configApiRef,\n createExtensionInput,\n createFrontendModule,\n createFrontendPlugin,\n PageBlueprint,\n translationApiRef,\n} from '@backstage/frontend-plugin-api';\nimport ExtensionIcon from '@mui/icons-material/Extension';\nimport {\n AppRootWrapperBlueprint,\n TranslationBlueprint,\n} from '@backstage/plugin-app-react';\n\nimport {\n translationsPluginTranslationRef,\n translationsPluginTranslations,\n} from './translations';\nimport { I18nextTranslationApi } from './apis/I18nextTranslationApi';\nimport { attachPseudolocalizationIfEnabled } from './apis/pseudolocalization';\nimport { PseudoLocalizationProvider } from './components/PseudoLocalizationProvider';\n\nconst translationsPage = PageBlueprint.make({\n params: {\n path: '/translations',\n title: 'Translations',\n icon: <ExtensionIcon />,\n noHeader: true,\n loader: () =>\n import('./components/TranslationsPage').then(m => <m.TranslationsPage />),\n },\n});\n\nconst i18nTranslation = TranslationBlueprint.make({\n name: 'translations-i18n',\n params: {\n resource: translationsPluginTranslations,\n },\n});\n\nconst translationsApiExtension = ApiBlueprint.makeWithOverrides({\n name: 'translations',\n inputs: {\n translations: createExtensionInput(\n [TranslationBlueprint.dataRefs.translation],\n { replaces: [{ id: 'app', input: 'translations' }], internal: true },\n ),\n },\n factory: (originalFactory, { inputs }) => {\n return originalFactory(defineParams =>\n defineParams({\n api: translationApiRef,\n deps: { languageApi: appLanguageApiRef, configApi: configApiRef },\n factory: ({ languageApi, configApi }) => {\n const resources = inputs.translations.map(i =>\n i.get(TranslationBlueprint.dataRefs.translation),\n );\n const api = I18nextTranslationApi.create({\n languageApi,\n resources,\n });\n attachPseudolocalizationIfEnabled(api, configApi);\n return api;\n },\n }),\n );\n },\n});\n\nconst pseudoLocalizationWrapper = AppRootWrapperBlueprint.make({\n name: 'pseudo-localization',\n params: {\n component: PseudoLocalizationProvider,\n },\n});\n\n/**\n * The Translations frontend plugin for the new Backstage frontend system.\n *\n * @public\n */\nexport default createFrontendPlugin({\n pluginId: 'translations',\n extensions: [translationsPage, i18nTranslation],\n});\n\n/**\n * Module that replaces the built-in TranslationApi with one that\n * exposes `getI18nInstance()`, needed by the translations debug page\n * and pseudo-localization. Also attaches pseudo-localization when\n * enabled via app-config or URL param.\n *\n * @public\n */\nexport const translationsApiModule = createFrontendModule({\n pluginId: 'app',\n extensions: [translationsApiExtension],\n});\n\n/**\n * Pseudo-localization module for OFS / dynamic plugins.\n * In NFS, pseudo-localization is handled by `translationsApiModule` instead.\n *\n * @public\n */\nexport const translationsPseudoLocalizationModule = createFrontendModule({\n pluginId: 'app',\n extensions: [pseudoLocalizationWrapper],\n});\n\nexport { translationsPluginTranslationRef, translationsPluginTranslations };\n"],"names":[],"mappings":";;;;;;;;;;AAwCA,MAAM,gBAAA,GAAmB,cAAc,IAAA,CAAK;AAAA,EAC1C,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,eAAA;AAAA,IACN,KAAA,EAAO,cAAA;AAAA,IACP,IAAA,sBAAO,aAAA,EAAA,EAAc,CAAA;AAAA,IACrB,QAAA,EAAU,IAAA;AAAA,IACV,MAAA,EAAQ,MACN,OAAO,sCAA+B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAAK,GAAA,CAAC,CAAA,CAAE,gBAAA,EAAF,EAAmB,CAAE;AAAA;AAE9E,CAAC,CAAA;AAED,MAAM,eAAA,GAAkB,qBAAqB,IAAA,CAAK;AAAA,EAChD,IAAA,EAAM,mBAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU;AAAA;AAEd,CAAC,CAAA;AAED,MAAM,wBAAA,GAA2B,aAAa,iBAAA,CAAkB;AAAA,EAC9D,IAAA,EAAM,cAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,YAAA,EAAc,oBAAA;AAAA,MACZ,CAAC,oBAAA,CAAqB,QAAA,CAAS,WAAW,CAAA;AAAA,MAC1C,EAAE,QAAA,EAAU,CAAC,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,cAAA,EAAgB,CAAA,EAAG,QAAA,EAAU,IAAA;AAAK;AACrE,GACF;AAAA,EACA,OAAA,EAAS,CAAC,eAAA,EAAiB,EAAE,QAAO,KAAM;AACxC,IAAA,OAAO,eAAA;AAAA,MAAgB,kBACrB,YAAA,CAAa;AAAA,QACX,GAAA,EAAK,iBAAA;AAAA,QACL,IAAA,EAAM,EAAE,WAAA,EAAa,iBAAA,EAAmB,WAAW,YAAA,EAAa;AAAA,QAChE,OAAA,EAAS,CAAC,EAAE,WAAA,EAAa,WAAU,KAAM;AACvC,UAAA,MAAM,SAAA,GAAY,OAAO,YAAA,CAAa,GAAA;AAAA,YAAI,CAAA,CAAA,KACxC,CAAA,CAAE,GAAA,CAAI,oBAAA,CAAqB,SAAS,WAAW;AAAA,WACjD;AACA,UAAA,MAAM,GAAA,GAAM,sBAAsB,MAAA,CAAO;AAAA,YACvC,WAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA,iCAAA,CAAkC,KAAK,SAAS,CAAA;AAChD,UAAA,OAAO,GAAA;AAAA,QACT;AAAA,OACD;AAAA,KACH;AAAA,EACF;AACF,CAAC,CAAA;AAED,MAAM,yBAAA,GAA4B,wBAAwB,IAAA,CAAK;AAAA,EAC7D,IAAA,EAAM,qBAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,SAAA,EAAW;AAAA;AAEf,CAAC,CAAA;AAOD,YAAe,oBAAA,CAAqB;AAAA,EAClC,QAAA,EAAU,cAAA;AAAA,EACV,UAAA,EAAY,CAAC,gBAAA,EAAkB,eAAe;AAChD,CAAC,CAAA;AAUM,MAAM,wBAAwB,oBAAA,CAAqB;AAAA,EACxD,QAAA,EAAU,KAAA;AAAA,EACV,UAAA,EAAY,CAAC,wBAAwB;AACvC,CAAC;AAQM,MAAM,uCAAuC,oBAAA,CAAqB;AAAA,EACvE,QAAA,EAAU,KAAA;AAAA,EACV,UAAA,EAAY,CAAC,yBAAyB;AACxC,CAAC;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/index.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ApiBlueprint,\n appLanguageApiRef,\n configApiRef,\n createExtensionInput,\n createFrontendModule,\n createFrontendPlugin,\n fetchApiRef,\n PageBlueprint,\n translationApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { discoveryApiRef } from '@backstage/core-plugin-api';\nimport ExtensionIcon from '@mui/icons-material/Extension';\nimport {\n AppRootWrapperBlueprint,\n TranslationBlueprint,\n} from '@backstage/plugin-app-react';\n\nimport {\n translationsPluginTranslationRef,\n translationsPluginTranslations,\n} from './translations';\nimport { I18nextTranslationApi } from './apis/I18nextTranslationApi';\nimport {\n createJsonTranslationsLoader,\n JsonTranslations,\n} from './apis/jsonTranslations';\nimport { attachPseudolocalizationIfEnabled } from './apis/pseudolocalization';\nimport { PseudoLocalizationProvider } from './components/PseudoLocalizationProvider';\n\nconst JSON_TRANSLATIONS_TIMEOUT_MS = 10_000;\n\nconst translationsPage = PageBlueprint.make({\n params: {\n path: '/translations',\n title: 'Translations',\n icon: <ExtensionIcon />,\n noHeader: true,\n loader: () =>\n import('./components/TranslationsPage').then(m => <m.TranslationsPage />),\n },\n});\n\nconst i18nTranslation = TranslationBlueprint.make({\n name: 'translations-i18n',\n params: {\n resource: translationsPluginTranslations,\n },\n});\n\nconst translationsApiExtension = ApiBlueprint.makeWithOverrides({\n name: 'translations',\n inputs: {\n translations: createExtensionInput(\n [TranslationBlueprint.dataRefs.translation],\n { replaces: [{ id: 'app', input: 'translations' }], internal: true },\n ),\n },\n factory: (originalFactory, { inputs }) => {\n return originalFactory(defineParams =>\n defineParams({\n api: translationApiRef,\n deps: {\n languageApi: appLanguageApiRef,\n configApi: configApiRef,\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ languageApi, configApi, discoveryApi, fetchApi }) => {\n const resources = inputs.translations.map(i =>\n i.get(TranslationBlueprint.dataRefs.translation),\n );\n const requestJsonTranslations =\n async (): Promise<JsonTranslations> => {\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n controller.abort();\n reject(\n new Error(\n `Timed out after ${JSON_TRANSLATIONS_TIMEOUT_MS}ms`,\n ),\n );\n }, JSON_TRANSLATIONS_TIMEOUT_MS);\n });\n const request = discoveryApi\n .getBaseUrl('translations')\n .then((baseUrl: string) =>\n fetchApi.fetch(baseUrl, { signal: controller.signal }),\n )\n .then((response: Response) => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return response.json();\n });\n\n try {\n return await Promise.race([request, timeout]);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n };\n const loadJsonTranslations = createJsonTranslationsLoader(\n requestJsonTranslations,\n (error: unknown) => {\n // Keep plugin resources usable when the backend is unavailable.\n // eslint-disable-next-line no-console\n console.warn(`Unable to load JSON translations: ${error}`);\n },\n );\n\n const api = I18nextTranslationApi.create({\n languageApi,\n resources,\n loadJsonTranslations,\n });\n attachPseudolocalizationIfEnabled(api, configApi);\n return api;\n },\n }),\n );\n },\n});\n\nconst pseudoLocalizationWrapper = AppRootWrapperBlueprint.make({\n name: 'pseudo-localization',\n params: {\n component: PseudoLocalizationProvider,\n },\n});\n\n/**\n * The Translations frontend plugin for the new Backstage frontend system.\n *\n * @public\n */\nexport default createFrontendPlugin({\n pluginId: 'translations',\n extensions: [translationsPage, i18nTranslation],\n});\n\n/**\n * Module that replaces the built-in TranslationApi with one that\n * exposes `getI18nInstance()`, needed by the translations debug page\n * and pseudo-localization. Also attaches pseudo-localization when\n * enabled via app-config or URL param.\n *\n * @public\n */\nexport const translationsApiModule = createFrontendModule({\n pluginId: 'app',\n extensions: [translationsApiExtension],\n});\n\n/**\n * Pseudo-localization module for OFS / dynamic plugins.\n * In NFS, pseudo-localization is handled by `translationsApiModule` instead.\n *\n * @public\n */\nexport const translationsPseudoLocalizationModule = createFrontendModule({\n pluginId: 'app',\n extensions: [pseudoLocalizationWrapper],\n});\n\nexport { translationsPluginTranslationRef, translationsPluginTranslations };\n"],"names":[],"mappings":"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"}
package/dist/legacy.d.ts CHANGED
@@ -11,6 +11,8 @@ import { PropsWithChildren } from 'react';
11
11
  interface I18nextTranslationApiOptions {
12
12
  languageApi: AppLanguageApi;
13
13
  resources?: Array<TranslationMessages | TranslationResource>;
14
+ /** JSON catalog and pod-mounted overrides served by translations-backend. */
15
+ loadJsonTranslations?: () => Promise<Record<string, Record<string, Record<string, string>>>>;
14
16
  }
15
17
  /** @public */
16
18
  declare class I18nextTranslationApi implements TranslationApi {
@@ -12,7 +12,7 @@ const translationsTranslationDe = createTranslationMessages({
12
12
  "export.title": "\xDCbersetzungen",
13
13
  "export.downloadButton": "Standard\xFCbersetzungen herunterladen (Englisch)",
14
14
  "export.filename": "translations-{{timestamp}}.json",
15
- "common.loading": "Ladevorgang l\xE4uft...",
15
+ "common.loading": "Wird geladen...",
16
16
  "common.error": "Es ist ein Fehler aufgetreten",
17
17
  "common.noData": "Keine Daten verf\xFCgbar",
18
18
  "common.refresh": "Aktualisieren",
@@ -1 +1 @@
1
- {"version":3,"file":"de.esm.js","sources":["../../src/translations/de.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * de translation for plugin.translations.\n * @public\n */\nconst translationsTranslationDe = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Übersetzungen',\n 'page.subtitle': 'Geladene Übersetzungen verwalten und anzeigen',\n 'table.title': 'Geladene Übersetzungen ({{count}})',\n 'table.options.pageSize': 'Elemente pro Seite',\n 'table.options.pageSizeOptions': '{{count}} Elemente anzeigen',\n 'export.title': 'Übersetzungen',\n 'export.downloadButton': 'Standardübersetzungen herunterladen (Englisch)',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': 'Ladevorgang läuft...',\n 'common.error': 'Es ist ein Fehler aufgetreten',\n 'common.noData': 'Keine Daten verfügbar',\n 'common.refresh': 'Aktualisieren',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationDe;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,kBAAA;AAAA,IACd,eAAA,EAAiB,kDAAA;AAAA,IACjB,aAAA,EAAe,uCAAA;AAAA,IACf,wBAAA,EAA0B,oBAAA;AAAA,IAC1B,+BAAA,EAAiC,6BAAA;AAAA,IACjC,cAAA,EAAgB,kBAAA;AAAA,IAChB,uBAAA,EAAyB,mDAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,yBAAA;AAAA,IAClB,cAAA,EAAgB,+BAAA;AAAA,IAChB,eAAA,EAAiB,0BAAA;AAAA,IACjB,gBAAA,EAAkB,eAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
1
+ {"version":3,"file":"de.esm.js","sources":["../../src/translations/de.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * de translation for plugin.translations.\n * @public\n */\nconst translationsTranslationDe = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Übersetzungen',\n 'page.subtitle': 'Geladene Übersetzungen verwalten und anzeigen',\n 'table.title': 'Geladene Übersetzungen ({{count}})',\n 'table.options.pageSize': 'Elemente pro Seite',\n 'table.options.pageSizeOptions': '{{count}} Elemente anzeigen',\n 'export.title': 'Übersetzungen',\n 'export.downloadButton': 'Standardübersetzungen herunterladen (Englisch)',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': 'Wird geladen...',\n 'common.error': 'Es ist ein Fehler aufgetreten',\n 'common.noData': 'Keine Daten verfügbar',\n 'common.refresh': 'Aktualisieren',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationDe;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,kBAAA;AAAA,IACd,eAAA,EAAiB,kDAAA;AAAA,IACjB,aAAA,EAAe,uCAAA;AAAA,IACf,wBAAA,EAA0B,oBAAA;AAAA,IAC1B,+BAAA,EAAiC,6BAAA;AAAA,IACjC,cAAA,EAAgB,kBAAA;AAAA,IAChB,uBAAA,EAAyB,mDAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,iBAAA;AAAA,IAClB,cAAA,EAAgB,+BAAA;AAAA,IAChB,eAAA,EAAiB,0BAAA;AAAA,IACjB,gBAAA,EAAkB,eAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
@@ -5,19 +5,19 @@ const translationsTranslationFr = createTranslationMessages({
5
5
  ref: translationsPluginTranslationRef,
6
6
  messages: {
7
7
  "page.title": "Traductions",
8
- "page.subtitle": "G\xE9rer et afficher les traductions charg\xE9es",
8
+ "page.subtitle": "G\xE9rer et consulter les traductions charg\xE9es",
9
9
  "table.title": "Traductions charg\xE9es ({{count}})",
10
- "table.headers.refId": "ID de r\xE9f",
10
+ "table.headers.refId": "ID de r\xE9f\xE9rence",
11
11
  "table.headers.key": "Cl\xE9",
12
- "table.options.pageSize": "Articles par page",
12
+ "table.options.pageSize": "\xC9l\xE9ments par page",
13
13
  "table.options.pageSizeOptions": "Afficher {{count}} \xE9l\xE9ments",
14
14
  "export.title": "Traductions",
15
15
  "export.downloadButton": "T\xE9l\xE9charger les traductions par d\xE9faut (anglais)",
16
16
  "export.filename": "traductions-{{timestamp}}.json",
17
- "common.loading": "Chargement en cours...",
18
- "common.error": "Une erreur s'est produite",
19
- "common.noData": "Aucune donn\xE9e disponible",
20
- "common.refresh": "Rafra\xEEchir",
17
+ "common.loading": "Chargement...",
18
+ "common.error": "Une erreur s\u2019est produite",
19
+ "common.noData": "Pas de donn\xE9es disponibles",
20
+ "common.refresh": "Actualiser",
21
21
  "language.displayFormat": "{{displayName}} ({{code}})"
22
22
  }
23
23
  });
@@ -1 +1 @@
1
- {"version":3,"file":"fr.esm.js","sources":["../../src/translations/fr.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * fr translation for plugin.translations.\n * @public\n */\nconst translationsTranslationFr = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Traductions',\n 'page.subtitle': 'Gérer et afficher les traductions chargées',\n 'table.title': 'Traductions chargées ({{count}})',\n 'table.headers.refId': 'ID de réf',\n 'table.headers.key': 'Clé',\n 'table.options.pageSize': 'Articles par page',\n 'table.options.pageSizeOptions': 'Afficher {{count}} éléments',\n 'export.title': 'Traductions',\n 'export.downloadButton': 'Télécharger les traductions par défaut (anglais)',\n 'export.filename': 'traductions-{{timestamp}}.json',\n 'common.loading': 'Chargement en cours...',\n 'common.error': \"Une erreur s'est produite\",\n 'common.noData': 'Aucune donnée disponible',\n 'common.refresh': 'Rafraîchir',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationFr;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,aAAA;AAAA,IACd,eAAA,EAAiB,kDAAA;AAAA,IACjB,aAAA,EAAe,qCAAA;AAAA,IACf,qBAAA,EAAuB,cAAA;AAAA,IACvB,mBAAA,EAAqB,QAAA;AAAA,IACrB,wBAAA,EAA0B,mBAAA;AAAA,IAC1B,+BAAA,EAAiC,mCAAA;AAAA,IACjC,cAAA,EAAgB,aAAA;AAAA,IAChB,uBAAA,EAAyB,2DAAA;AAAA,IACzB,iBAAA,EAAmB,gCAAA;AAAA,IACnB,gBAAA,EAAkB,wBAAA;AAAA,IAClB,cAAA,EAAgB,2BAAA;AAAA,IAChB,eAAA,EAAiB,6BAAA;AAAA,IACjB,gBAAA,EAAkB,eAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
1
+ {"version":3,"file":"fr.esm.js","sources":["../../src/translations/fr.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * fr translation for plugin.translations.\n * @public\n */\nconst translationsTranslationFr = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Traductions',\n 'page.subtitle': 'Gérer et consulter les traductions chargées',\n 'table.title': 'Traductions chargées ({{count}})',\n 'table.headers.refId': 'ID de référence',\n 'table.headers.key': 'Clé',\n 'table.options.pageSize': 'Éléments par page',\n 'table.options.pageSizeOptions': 'Afficher {{count}} éléments',\n 'export.title': 'Traductions',\n 'export.downloadButton': 'Télécharger les traductions par défaut (anglais)',\n 'export.filename': 'traductions-{{timestamp}}.json',\n 'common.loading': 'Chargement...',\n 'common.error': 'Une erreur s’est produite',\n 'common.noData': 'Pas de données disponibles',\n 'common.refresh': 'Actualiser',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationFr;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,aAAA;AAAA,IACd,eAAA,EAAiB,mDAAA;AAAA,IACjB,aAAA,EAAe,qCAAA;AAAA,IACf,qBAAA,EAAuB,uBAAA;AAAA,IACvB,mBAAA,EAAqB,QAAA;AAAA,IACrB,wBAAA,EAA0B,yBAAA;AAAA,IAC1B,+BAAA,EAAiC,mCAAA;AAAA,IACjC,cAAA,EAAgB,aAAA;AAAA,IAChB,uBAAA,EAAyB,2DAAA;AAAA,IACzB,iBAAA,EAAmB,gCAAA;AAAA,IACnB,gBAAA,EAAkB,eAAA;AAAA,IAClB,cAAA,EAAgB,gCAAA;AAAA,IAChB,eAAA,EAAiB,+BAAA;AAAA,IACjB,gBAAA,EAAkB,YAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
@@ -5,14 +5,14 @@ const translationsTranslationIt = createTranslationMessages({
5
5
  ref: translationsPluginTranslationRef,
6
6
  messages: {
7
7
  "page.title": "Traduzioni",
8
- "page.subtitle": "Gestione e visualizzazione delle traduzioni caricate",
8
+ "page.subtitle": "Gestisci e visualizza le traduzioni caricate",
9
9
  "table.title": "Traduzioni caricate ({{count}})",
10
10
  "table.headers.refId": "ID di riferimento",
11
11
  "table.headers.key": "Chiave",
12
12
  "table.options.pageSize": "Elementi per pagina",
13
13
  "table.options.pageSizeOptions": "Mostra {{count}} elementi",
14
14
  "export.title": "Traduzioni",
15
- "export.downloadButton": "Scarica le traduzioni predefinite (italiano)",
15
+ "export.downloadButton": "Scarica le traduzioni predefinite (inglese)",
16
16
  "export.filename": "translations-{{timestamp}}.json",
17
17
  "common.loading": "Caricamento...",
18
18
  "common.error": "Si \xE8 verificato un errore",
@@ -1 +1 @@
1
- {"version":3,"file":"it.esm.js","sources":["../../src/translations/it.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * Italian translation for translations.\n * @public\n */\nconst translationsTranslationIt = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Traduzioni',\n 'page.subtitle': 'Gestione e visualizzazione delle traduzioni caricate',\n 'table.title': 'Traduzioni caricate ({{count}})',\n 'table.headers.refId': 'ID di riferimento',\n 'table.headers.key': 'Chiave',\n 'table.options.pageSize': 'Elementi per pagina',\n 'table.options.pageSizeOptions': 'Mostra {{count}} elementi',\n 'export.title': 'Traduzioni',\n 'export.downloadButton': 'Scarica le traduzioni predefinite (italiano)',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': 'Caricamento...',\n 'common.error': 'Si è verificato un errore',\n 'common.noData': 'Nessun dato disponibile',\n 'common.refresh': 'Aggiorna',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationIt;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,YAAA;AAAA,IACd,eAAA,EAAiB,sDAAA;AAAA,IACjB,aAAA,EAAe,iCAAA;AAAA,IACf,qBAAA,EAAuB,mBAAA;AAAA,IACvB,mBAAA,EAAqB,QAAA;AAAA,IACrB,wBAAA,EAA0B,qBAAA;AAAA,IAC1B,+BAAA,EAAiC,2BAAA;AAAA,IACjC,cAAA,EAAgB,YAAA;AAAA,IAChB,uBAAA,EAAyB,8CAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,gBAAA;AAAA,IAClB,cAAA,EAAgB,8BAAA;AAAA,IAChB,eAAA,EAAiB,yBAAA;AAAA,IACjB,gBAAA,EAAkB,UAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
1
+ {"version":3,"file":"it.esm.js","sources":["../../src/translations/it.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * Italian translation for translations.\n * @public\n */\nconst translationsTranslationIt = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': 'Traduzioni',\n 'page.subtitle': 'Gestisci e visualizza le traduzioni caricate',\n 'table.title': 'Traduzioni caricate ({{count}})',\n 'table.headers.refId': 'ID di riferimento',\n 'table.headers.key': 'Chiave',\n 'table.options.pageSize': 'Elementi per pagina',\n 'table.options.pageSizeOptions': 'Mostra {{count}} elementi',\n 'export.title': 'Traduzioni',\n 'export.downloadButton': 'Scarica le traduzioni predefinite (inglese)',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': 'Caricamento...',\n 'common.error': 'Si è verificato un errore',\n 'common.noData': 'Nessun dato disponibile',\n 'common.refresh': 'Aggiorna',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationIt;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,YAAA;AAAA,IACd,eAAA,EAAiB,8CAAA;AAAA,IACjB,aAAA,EAAe,iCAAA;AAAA,IACf,qBAAA,EAAuB,mBAAA;AAAA,IACvB,mBAAA,EAAqB,QAAA;AAAA,IACrB,wBAAA,EAA0B,qBAAA;AAAA,IAC1B,+BAAA,EAAiC,2BAAA;AAAA,IACjC,cAAA,EAAgB,YAAA;AAAA,IAChB,uBAAA,EAAyB,6CAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,gBAAA;AAAA,IAClB,cAAA,EAAgB,8BAAA;AAAA,IAChB,eAAA,EAAiB,yBAAA;AAAA,IACjB,gBAAA,EAAkB,UAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
@@ -5,14 +5,14 @@ const translationsTranslationJa = createTranslationMessages({
5
5
  ref: translationsPluginTranslationRef,
6
6
  messages: {
7
7
  "page.title": "\u7FFB\u8A33",
8
- "page.subtitle": "\u8AAD\u307F\u8FBC\u307E\u308C\u305F\u7FFB\u8A33\u306E\u7BA1\u7406\u304A\u3088\u3073\u8868\u793A",
8
+ "page.subtitle": "\u8AAD\u307F\u8FBC\u307E\u308C\u305F\u7FFB\u8A33\u3092\u7BA1\u7406\u304A\u3088\u3073\u8868\u793A\u3057\u307E\u3059",
9
9
  "table.title": "\u8AAD\u307F\u8FBC\u307E\u308C\u305F\u7FFB\u8A33 ({{count}})",
10
10
  "table.headers.refId": "\u53C2\u7167 ID",
11
11
  "table.headers.key": "\u30AD\u30FC",
12
- "table.options.pageSize": "1 \u30DA\u30FC\u30B8\u306E\u9805\u76EE\u6570",
13
- "table.options.pageSizeOptions": "{{count}} \u4EF6\u306E\u9805\u76EE\u3092\u8868\u793A",
12
+ "table.options.pageSize": "1 \u30DA\u30FC\u30B8\u3042\u305F\u308A\u306E\u9805\u76EE\u6570",
13
+ "table.options.pageSizeOptions": "{{count}} \u4EF6\u306E\u9805\u76EE\u3092\u8868\u793A\u3059\u308B",
14
14
  "export.title": "\u7FFB\u8A33",
15
- "export.downloadButton": "\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u7FFB\u8A33\u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9 (\u82F1\u8A9E)",
15
+ "export.downloadButton": "\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u7FFB\u8A33 (\u82F1\u8A9E) \u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3059\u308B",
16
16
  "export.filename": "translations-{{timestamp}}.json",
17
17
  "common.loading": "\u8AAD\u307F\u8FBC\u307F\u4E2D...",
18
18
  "common.error": "\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F",
@@ -1 +1 @@
1
- {"version":3,"file":"ja.esm.js","sources":["../../src/translations/ja.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * Japanese translation for translations.\n * @public\n */\nconst translationsTranslationJa = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': '翻訳',\n 'page.subtitle': '読み込まれた翻訳の管理および表示',\n 'table.title': '読み込まれた翻訳 ({{count}})',\n 'table.headers.refId': '参照 ID',\n 'table.headers.key': 'キー',\n 'table.options.pageSize': '1 ページの項目数',\n 'table.options.pageSizeOptions': '{{count}} 件の項目を表示',\n 'export.title': '翻訳',\n 'export.downloadButton': 'デフォルトの翻訳のダウンロード (英語)',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': '読み込み中...',\n 'common.error': 'エラーが発生しました',\n 'common.noData': '利用可能なデータはありません',\n 'common.refresh': '更新',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationJa;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,cAAA;AAAA,IACd,eAAA,EAAiB,kGAAA;AAAA,IACjB,aAAA,EAAe,8DAAA;AAAA,IACf,qBAAA,EAAuB,iBAAA;AAAA,IACvB,mBAAA,EAAqB,cAAA;AAAA,IACrB,wBAAA,EAA0B,8CAAA;AAAA,IAC1B,+BAAA,EAAiC,sDAAA;AAAA,IACjC,cAAA,EAAgB,cAAA;AAAA,IAChB,uBAAA,EAAyB,2GAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,mCAAA;AAAA,IAClB,cAAA,EAAgB,8DAAA;AAAA,IAChB,eAAA,EAAiB,sFAAA;AAAA,IACjB,gBAAA,EAAkB,cAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
1
+ {"version":3,"file":"ja.esm.js","sources":["../../src/translations/ja.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationMessages } from '@backstage/core-plugin-api/alpha';\nimport { translationsPluginTranslationRef } from './ref';\n\n/**\n * Japanese translation for translations.\n * @public\n */\nconst translationsTranslationJa = createTranslationMessages({\n ref: translationsPluginTranslationRef,\n messages: {\n 'page.title': '翻訳',\n 'page.subtitle': '読み込まれた翻訳を管理および表示します',\n 'table.title': '読み込まれた翻訳 ({{count}})',\n 'table.headers.refId': '参照 ID',\n 'table.headers.key': 'キー',\n 'table.options.pageSize': '1 ページあたりの項目数',\n 'table.options.pageSizeOptions': '{{count}} 件の項目を表示する',\n 'export.title': '翻訳',\n 'export.downloadButton': 'デフォルトの翻訳 (英語) をダウンロードする',\n 'export.filename': 'translations-{{timestamp}}.json',\n 'common.loading': '読み込み中...',\n 'common.error': 'エラーが発生しました',\n 'common.noData': '利用可能なデータはありません',\n 'common.refresh': '更新',\n 'language.displayFormat': '{{displayName}} ({{code}})',\n },\n});\n\nexport default translationsTranslationJa;\n"],"names":[],"mappings":";;;AAuBA,MAAM,4BAA4B,yBAAA,CAA0B;AAAA,EAC1D,GAAA,EAAK,gCAAA;AAAA,EACL,QAAA,EAAU;AAAA,IACR,YAAA,EAAc,cAAA;AAAA,IACd,eAAA,EAAiB,oHAAA;AAAA,IACjB,aAAA,EAAe,8DAAA;AAAA,IACf,qBAAA,EAAuB,iBAAA;AAAA,IACvB,mBAAA,EAAqB,cAAA;AAAA,IACrB,wBAAA,EAA0B,gEAAA;AAAA,IAC1B,+BAAA,EAAiC,kEAAA;AAAA,IACjC,cAAA,EAAgB,cAAA;AAAA,IAChB,uBAAA,EAAyB,wHAAA;AAAA,IACzB,iBAAA,EAAmB,iCAAA;AAAA,IACnB,gBAAA,EAAkB,mCAAA;AAAA,IAClB,cAAA,EAAgB,8DAAA;AAAA,IAChB,eAAA,EAAiB,sFAAA;AAAA,IACjB,gBAAA,EAAkB,cAAA;AAAA,IAClB,wBAAA,EAA0B;AAAA;AAE9B,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-translations",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -76,16 +76,6 @@
76
76
  "./translations-pseudo-localization-module": "@backstage/FrontendModule"
77
77
  }
78
78
  },
79
- "scalprum": {
80
- "name": "red-hat-developer-hub.backstage-plugin-translations",
81
- "exposedModules": {
82
- "PluginRoot": "./src/index.tsx",
83
- "Alpha": "./src/alpha.ts",
84
- "Legacy": "./src/legacy.ts",
85
- "TranslationsApiModule": "./src/translationsApiModuleExport.ts",
86
- "TranslationsPseudoLocalizationModule": "./src/translationsPseudoLocalizationModuleExport.ts"
87
- }
88
- },
89
79
  "sideEffects": false,
90
80
  "scripts": {
91
81
  "start": "backstage-cli package start",
@@ -100,6 +90,8 @@
100
90
  "dependencies": {
101
91
  "@backstage/core-components": "^0.18.13",
102
92
  "@backstage/core-plugin-api": "^1.12.9",
93
+ "@backstage/frontend-plugin-api": "^0.18.0",
94
+ "@backstage/plugin-app-react": "^0.2.6",
103
95
  "@backstage/plugin-user-settings": "^0.9.6",
104
96
  "@backstage/theme": "^0.7.3",
105
97
  "@backstage/types": "^1.2.2",