@backstage/plugin-app 0.1.9-next.0 → 0.1.9-next.2

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,32 @@
1
1
  # @backstage/plugin-app
2
2
 
3
+ ## 0.1.9-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/core-plugin-api@1.10.7-next.0
9
+ - @backstage/core-components@0.17.2-next.1
10
+ - @backstage/frontend-plugin-api@0.10.2-next.1
11
+ - @backstage/integration-react@1.2.7-next.2
12
+ - @backstage/plugin-permission-react@0.4.34-next.1
13
+ - @backstage/theme@0.6.6-next.0
14
+ - @backstage/types@1.2.1
15
+
16
+ ## 0.1.9-next.1
17
+
18
+ ### Patch Changes
19
+
20
+ - fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
21
+ - Updated dependencies
22
+ - @backstage/theme@0.6.6-next.0
23
+ - @backstage/core-components@0.17.2-next.0
24
+ - @backstage/frontend-plugin-api@0.10.2-next.0
25
+ - @backstage/integration-react@1.2.7-next.1
26
+ - @backstage/core-plugin-api@1.10.6
27
+ - @backstage/types@1.2.1
28
+ - @backstage/plugin-permission-react@0.4.34-next.0
29
+
3
30
  ## 0.1.9-next.0
4
31
 
5
32
  ### Patch Changes
@@ -3,6 +3,7 @@ import ObservableImpl from 'zen-observable';
3
3
  import { toInternalTranslationResource } from '../../../../../core-plugin-api/src/translation/TranslationResource.esm.js';
4
4
  import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef.esm.js';
5
5
  import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector.esm.js';
6
+ import { isValidElement, createElement, Fragment } from 'react';
6
7
 
7
8
  function removeNulls(messages) {
8
9
  return Object.fromEntries(
@@ -69,6 +70,65 @@ class ResourceLoader {
69
70
  await load;
70
71
  }
71
72
  }
73
+ class JsxInterpolator {
74
+ #setFormatHook;
75
+ #marker;
76
+ #pattern;
77
+ static fromI18n(i18n) {
78
+ const interpolator = i18n.services.interpolator;
79
+ const originalFormat = interpolator.format;
80
+ let formatHook;
81
+ interpolator.format = (value, format, lng, formatOpts) => {
82
+ if (format) {
83
+ return originalFormat(value, format, lng, formatOpts);
84
+ }
85
+ return formatHook?.(value, format, lng, formatOpts) ?? value;
86
+ };
87
+ return new JsxInterpolator(
88
+ // Using a random marker to ensure it can't be misused
89
+ Math.random().toString(36).substring(2, 8),
90
+ (hook) => {
91
+ formatHook = hook;
92
+ }
93
+ );
94
+ }
95
+ constructor(marker, setFormatHook) {
96
+ this.#setFormatHook = setFormatHook;
97
+ this.#marker = marker;
98
+ this.#pattern = new RegExp(`\\$${marker}\\(([^)]+)\\)`);
99
+ }
100
+ wrapT(originalT) {
101
+ return (key, options) => {
102
+ let elementsMap = void 0;
103
+ this.#setFormatHook((value) => {
104
+ if (isValidElement(value)) {
105
+ if (!elementsMap) {
106
+ elementsMap = /* @__PURE__ */ new Map();
107
+ }
108
+ const elementKey = elementsMap.size.toString();
109
+ elementsMap.set(elementKey, value);
110
+ return `$${this.#marker}(${elementKey})`;
111
+ }
112
+ return value;
113
+ });
114
+ const result = originalT(key, options);
115
+ if (!elementsMap) {
116
+ return result;
117
+ }
118
+ const split = result.split(this.#pattern);
119
+ return createElement(
120
+ Fragment,
121
+ null,
122
+ ...split.map((part, index) => {
123
+ if (index % 2 === 0) {
124
+ return part;
125
+ }
126
+ return elementsMap?.get(part);
127
+ }).filter(Boolean)
128
+ );
129
+ };
130
+ }
131
+ }
72
132
  class I18nextTranslationApi {
73
133
  static create(options) {
74
134
  const { languages } = options.languageApi.getAvailableLanguages();
@@ -76,7 +136,9 @@ class I18nextTranslationApi {
76
136
  fallbackLng: DEFAULT_LANGUAGE,
77
137
  supportedLngs: languages,
78
138
  interpolation: {
79
- escapeValue: false
139
+ escapeValue: false,
140
+ // Used for the JsxInterpolator format hook
141
+ alwaysFormat: true
80
142
  },
81
143
  ns: [],
82
144
  defaultNS: false,
@@ -88,6 +150,7 @@ class I18nextTranslationApi {
88
150
  if (!i18n.isInitialized) {
89
151
  throw new Error("i18next was unexpectedly not initialized");
90
152
  }
153
+ const interpolator = JsxInterpolator.fromI18n(i18n);
91
154
  const { language: initialLanguage } = options.languageApi.getLanguage();
92
155
  if (initialLanguage !== DEFAULT_LANGUAGE) {
93
156
  i18n.changeLanguage(initialLanguage);
@@ -123,7 +186,8 @@ class I18nextTranslationApi {
123
186
  const instance = new I18nextTranslationApi(
124
187
  i18n,
125
188
  loader,
126
- options.languageApi.getLanguage().language
189
+ options.languageApi.getLanguage().language,
190
+ interpolator
127
191
  );
128
192
  options.languageApi.language$().subscribe(({ language }) => {
129
193
  instance.#changeLanguage(language);
@@ -133,14 +197,16 @@ class I18nextTranslationApi {
133
197
  #i18n;
134
198
  #loader;
135
199
  #language;
200
+ #jsxInterpolator;
136
201
  /** Keep track of which refs we have registered default resources for */
137
202
  #registeredRefs = /* @__PURE__ */ new Set();
138
203
  /** Notify observers when language changes */
139
204
  #languageChangeListeners = /* @__PURE__ */ new Set();
140
- constructor(i18n, loader, language) {
205
+ constructor(i18n, loader, language, jsxInterpolator) {
141
206
  this.#i18n = i18n;
142
207
  this.#loader = loader;
143
208
  this.#language = language;
209
+ this.#jsxInterpolator = jsxInterpolator;
144
210
  }
145
211
  getTranslation(translationRef) {
146
212
  const internalRef = toInternalTranslationRef(translationRef);
@@ -199,10 +265,8 @@ class I18nextTranslationApi {
199
265
  if (this.#loader.needsLoading(this.#language, internalRef.id)) {
200
266
  return { ready: false };
201
267
  }
202
- const t = this.#i18n.getFixedT(
203
- null,
204
- internalRef.id
205
- );
268
+ const unwrappedT = this.#i18n.getFixedT(null, internalRef.id);
269
+ const t = this.#jsxInterpolator.wrapT(unwrappedT);
206
270
  return {
207
271
  ready: true,
208
272
  t
@@ -230,5 +294,5 @@ class I18nextTranslationApi {
230
294
  }
231
295
  }
232
296
 
233
- export { I18nextTranslationApi };
297
+ export { I18nextTranslationApi, JsxInterpolator };
234
298
  //# sourceMappingURL=I18nextTranslationApi.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"I18nextTranslationApi.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\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 { createInstance as createI18n, type i18n as I18n } from 'i18next';\nimport ObservableImpl from 'zen-observable';\n\n// Internal import to avoid code duplication, this will lead to duplication in build output\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalTranslationResource,\n InternalTranslationResourceLoader,\n} from '../../../../../core-plugin-api/src/translation/TranslationResource';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalTranslationRef,\n InternalTranslationRef,\n} from '../../../../../core-plugin-api/src/translation/TranslationRef';\nimport { Observable } from '@backstage/types';\nimport { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector';\n\n/** @alpha */\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/** @alpha */\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 },\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 { 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 );\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\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(i18n: I18n, loader: ResourceLoader, language: string) {\n this.#i18n = i18n;\n this.#loader = loader;\n this.#language = language;\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 t = this.#i18n.getFixedT(\n null,\n internalRef.id,\n ) as TranslationFunction<TMessages>;\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":["../../../../../../../../../packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\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\n// Internal import to avoid code duplication, this will lead to duplication in build output\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalTranslationResource,\n InternalTranslationResourceLoader,\n} from '../../../../../core-plugin-api/src/translation/TranslationResource';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalTranslationRef,\n InternalTranslationRef,\n} from '../../../../../core-plugin-api/src/translation/TranslationRef';\nimport { Observable } from '@backstage/types';\nimport { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector';\nimport { createElement, Fragment, ReactNode, isValidElement } from 'react';\n\n/** @alpha */\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/** @alpha */\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 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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"}
@@ -17,7 +17,7 @@ import { oauthRequestDialogAppRootElement, alertDisplayAppRootElement } from './
17
17
  import { apis } from './defaultApis.esm.js';
18
18
 
19
19
  const appPlugin = createFrontendPlugin({
20
- id: "app",
20
+ pluginId: "app",
21
21
  extensions: [
22
22
  ...apis,
23
23
  App,
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\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 { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n ComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n TranslationsApi,\n DefaultProgressComponent,\n DefaultNotFoundErrorPageComponent,\n DefaultErrorBoundaryComponent,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n DefaultSignInPage,\n dialogDisplayAppRootElement,\n} from './extensions';\nimport { apis } from './defaultApis';\n\n/** @public */\nexport const appPlugin = createFrontendPlugin({\n id: 'app',\n extensions: [\n ...apis,\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n ComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n TranslationsApi,\n DefaultProgressComponent,\n DefaultNotFoundErrorPageComponent,\n DefaultErrorBoundaryComponent,\n DefaultSignInPage,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n dialogDisplayAppRootElement,\n ],\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA0CO,MAAM,YAAY,oBAAqB,CAAA;AAAA,EAC5C,EAAI,EAAA,KAAA;AAAA,EACJ,UAAY,EAAA;AAAA,IACV,GAAG,IAAA;AAAA,IACH,GAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA,wBAAA;AAAA,IACA,iCAAA;AAAA,IACA,6BAAA;AAAA,IACA,iBAAA;AAAA,IACA,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA;AAAA;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\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 { createFrontendPlugin } from '@backstage/frontend-plugin-api';\nimport {\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n ComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n TranslationsApi,\n DefaultProgressComponent,\n DefaultNotFoundErrorPageComponent,\n DefaultErrorBoundaryComponent,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n DefaultSignInPage,\n dialogDisplayAppRootElement,\n} from './extensions';\nimport { apis } from './defaultApis';\n\n/** @public */\nexport const appPlugin = createFrontendPlugin({\n pluginId: 'app',\n extensions: [\n ...apis,\n App,\n AppLanguageApi,\n AppLayout,\n AppNav,\n AppRoot,\n AppRoutes,\n AppThemeApi,\n DarkTheme,\n LightTheme,\n ComponentsApi,\n IconsApi,\n FeatureFlagsApi,\n TranslationsApi,\n DefaultProgressComponent,\n DefaultNotFoundErrorPageComponent,\n DefaultErrorBoundaryComponent,\n DefaultSignInPage,\n oauthRequestDialogAppRootElement,\n alertDisplayAppRootElement,\n dialogDisplayAppRootElement,\n ],\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA0CO,MAAM,YAAY,oBAAqB,CAAA;AAAA,EAC5C,QAAU,EAAA,KAAA;AAAA,EACV,UAAY,EAAA;AAAA,IACV,GAAG,IAAA;AAAA,IACH,GAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA,wBAAA;AAAA,IACA,iCAAA;AAAA,IACA,6BAAA;AAAA,IACA,iBAAA;AAAA,IACA,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA;AAAA;AAEJ,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-app",
3
- "version": "0.1.9-next.0",
3
+ "version": "0.1.9-next.2",
4
4
  "backstage": {
5
5
  "role": "frontend-plugin",
6
6
  "pluginId": "app",
@@ -40,12 +40,12 @@
40
40
  "test": "backstage-cli package test"
41
41
  },
42
42
  "dependencies": {
43
- "@backstage/core-components": "0.17.1",
44
- "@backstage/core-plugin-api": "1.10.6",
45
- "@backstage/frontend-plugin-api": "0.10.1",
46
- "@backstage/integration-react": "1.2.7-next.0",
47
- "@backstage/plugin-permission-react": "0.4.33",
48
- "@backstage/theme": "0.6.5",
43
+ "@backstage/core-components": "0.17.2-next.1",
44
+ "@backstage/core-plugin-api": "1.10.7-next.0",
45
+ "@backstage/frontend-plugin-api": "0.10.2-next.1",
46
+ "@backstage/integration-react": "1.2.7-next.2",
47
+ "@backstage/plugin-permission-react": "0.4.34-next.1",
48
+ "@backstage/theme": "0.6.6-next.0",
49
49
  "@backstage/types": "1.2.1",
50
50
  "@material-ui/core": "^4.9.13",
51
51
  "@material-ui/icons": "^4.9.1",
@@ -53,9 +53,9 @@
53
53
  "react-use": "^17.2.4"
54
54
  },
55
55
  "devDependencies": {
56
- "@backstage/cli": "0.32.1-next.0",
57
- "@backstage/dev-utils": "1.1.10-next.0",
58
- "@backstage/frontend-test-utils": "0.3.2-next.0",
56
+ "@backstage/cli": "0.32.1-next.2",
57
+ "@backstage/dev-utils": "1.1.10-next.2",
58
+ "@backstage/frontend-test-utils": "0.3.2-next.2",
59
59
  "@testing-library/jest-dom": "^6.0.0",
60
60
  "@testing-library/react": "^16.0.0",
61
61
  "@testing-library/user-event": "^14.0.0",