From 536b5a4bdec900719f9a8fec555147596322f11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Sat, 28 Dec 2024 16:26:35 +0100 Subject: [PATCH 01/17] feat: tolgee SDK v6 --- docs.js | 2 +- js-sdk/migration_to_v6.mdx | 6 + js-sdk_versioned_docs/version-5.x.x/about.mdx | 53 +++ .../version-5.x.x/api/core_package/about.mdx | 15 + .../version-5.x.x/api/core_package/events.mdx | 84 ++++ .../api/core_package/format_simple.mdx | 47 ++ .../api/core_package/observer_options.mdx | 138 ++++++ .../api/core_package/options.mdx | 163 +++++++ .../api/core_package/other_tools.mdx | 38 ++ .../version-5.x.x/api/core_package/plugin.mdx | 157 +++++++ .../version-5.x.x/api/core_package/tolgee.mdx | 434 ++++++++++++++++++ .../version-5.x.x/api/packages.mdx | 32 ++ .../version-5.x.x/api/web_package/about.mdx | 29 ++ .../api/web_package/constants.mdx | 19 + .../version-5.x.x/api/web_package/plugins.mdx | 110 +++++ .../version-5.x.x/api/web_package/tools.mdx | 50 ++ .../version-5.x.x/filter_by_tags.mdx | 17 + .../version-5.x.x/formatting.mdx | 56 +++ .../version-5.x.x/get_started.mdx | 20 + .../version-5.x.x/in_context.mdx | 98 ++++ .../integrations/angular/api.mdx | 187 ++++++++ .../integrations/angular/html_tags.mdx | 35 ++ .../integrations/angular/installation.mdx | 93 ++++ .../integrations/angular/namespaces.mdx | 40 ++ .../integrations/angular/overview.mdx | 11 + .../angular/switching_language.mdx | 38 ++ .../integrations/angular/translating.mdx | 126 +++++ .../integrations/i18next/api.mdx | 45 ++ .../integrations/i18next/installation.mdx | 88 ++++ .../i18next/react_integration.mdx | 50 ++ .../integrations/i18next/vue_integration.mdx | 63 +++ .../version-5.x.x/integrations/react/api.mdx | 176 +++++++ .../integrations/react/installation.mdx | 94 ++++ .../integrations/react/next/app_router.mdx | 305 ++++++++++++ .../react/next/app_router_next_intl.mdx | 363 +++++++++++++++ .../integrations/react/next/introduction.mdx | 19 + .../integrations/react/next/pages_router.mdx | 193 ++++++++ .../next/shared/_AppRouterTranslating.mdx | 45 ++ .../shared/_LimitationsOfServerComponents.mdx | 5 + .../integrations/react/overview.mdx | 13 + .../integrations/react/react_native.mdx | 66 +++ .../version-5.x.x/integrations/react/ssr.mdx | 43 ++ .../react/switching_languages.mdx | 31 ++ .../integrations/react/tags_interpolation.mdx | 65 +++ .../integrations/react/translating.mdx | 64 +++ .../version-5.x.x/integrations/svelte/api.mdx | 128 ++++++ .../integrations/svelte/installation.mdx | 83 ++++ .../integrations/svelte/overview.mdx | 11 + .../svelte/switching_language.mdx | 27 ++ .../integrations/svelte/translating.mdx | 63 +++ .../integrations/vanilla/initialization.mdx | 53 +++ .../integrations/vanilla/installation.mdx | 58 +++ .../integrations/vanilla/translating.mdx | 37 ++ .../version-5.x.x/integrations/vue/api.mdx | 189 ++++++++ .../vue/component_interpolation.mdx | 57 +++ .../integrations/vue/installation.mdx | 100 ++++ .../integrations/vue/overview.mdx | 11 + .../version-5.x.x/integrations/vue/ssr.mdx | 79 ++++ .../integrations/vue/switching_languages.mdx | 29 ++ .../integrations/vue/translating.mdx | 75 +++ .../version-5.x.x/keys_tagging.mdx | 23 + .../version-5.x.x/language.mdx | 52 +++ .../version-5.x.x}/migration_to_v5/core.mdx | 0 .../migration_to_v5/i18next.mdx | 0 .../version-5.x.x}/migration_to_v5/ngx.mdx | 0 .../version-5.x.x}/migration_to_v5/react.mdx | 0 .../version-5.x.x}/migration_to_v5/svelte.mdx | 0 .../version-5.x.x}/migration_to_v5/vue.mdx | 0 .../version-5.x.x/namespaces.mdx | 48 ++ .../version-5.x.x/plugins.mdx | 30 ++ .../version-5.x.x/providing_static_data.mdx | 156 +++++++ .../version-5.x.x/shared/ExampleBanner.tsx | 19 + .../version-5.x.x/shared/_NamespacesHint.mdx | 3 + .../shared/_PreparingForProduction.mdx | 8 + .../version-5.x.x/shared/_TFunctionHint.mdx | 36 ++ .../version-5.x.x/text_observer.mdx | 57 +++ .../version-5.x.x/typed_keys.mdx | 40 ++ .../version-5.x.x/usage_without_platform.mdx | 44 ++ .../version-5.x.x/wrapping.mdx | 57 +++ .../version-5.x.x-sidebars.json | 155 +++++++ js-sdk_versions.json | 1 + sidebarJsSdk.js | 13 +- 82 files changed, 5555 insertions(+), 13 deletions(-) create mode 100644 js-sdk/migration_to_v6.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/about.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/about.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/events.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/format_simple.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/observer_options.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/options.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/other_tools.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/plugin.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/core_package/tolgee.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/packages.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/web_package/about.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/web_package/constants.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/web_package/plugins.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/api/web_package/tools.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/filter_by_tags.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/formatting.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/get_started.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/in_context.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/api.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/html_tags.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/namespaces.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/overview.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/switching_language.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/angular/translating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/i18next/api.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/i18next/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/i18next/react_integration.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/i18next/vue_integration.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/api.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router_next_intl.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/introduction.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/pages_router.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_AppRouterTranslating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_LimitationsOfServerComponents.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/overview.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/react_native.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/ssr.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/switching_languages.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/tags_interpolation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/react/translating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/svelte/api.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/svelte/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/svelte/overview.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/svelte/switching_language.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/svelte/translating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/initialization.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/translating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/api.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/component_interpolation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/installation.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/overview.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/ssr.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/switching_languages.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/integrations/vue/translating.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/keys_tagging.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/language.mdx rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/core.mdx (100%) rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/i18next.mdx (100%) rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/ngx.mdx (100%) rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/react.mdx (100%) rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/svelte.mdx (100%) rename {js-sdk => js-sdk_versioned_docs/version-5.x.x}/migration_to_v5/vue.mdx (100%) create mode 100644 js-sdk_versioned_docs/version-5.x.x/namespaces.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/plugins.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/providing_static_data.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/shared/ExampleBanner.tsx create mode 100644 js-sdk_versioned_docs/version-5.x.x/shared/_NamespacesHint.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/shared/_PreparingForProduction.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/shared/_TFunctionHint.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/text_observer.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/typed_keys.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/usage_without_platform.mdx create mode 100644 js-sdk_versioned_docs/version-5.x.x/wrapping.mdx create mode 100644 js-sdk_versioned_sidebars/version-5.x.x-sidebars.json diff --git a/docs.js b/docs.js index 1a01bf325..97c17bd93 100644 --- a/docs.js +++ b/docs.js @@ -30,7 +30,7 @@ const docs = [ versions: { current: { banner: 'none', - label: '5.x.x', + label: '6.x.x', }, }, }, diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx new file mode 100644 index 000000000..c6a9c2927 --- /dev/null +++ b/js-sdk/migration_to_v6.mdx @@ -0,0 +1,6 @@ +--- +id: migration-to-v6 +title: Migration to v6 +--- + + diff --git a/js-sdk_versioned_docs/version-5.x.x/about.mdx b/js-sdk_versioned_docs/version-5.x.x/about.mdx new file mode 100644 index 000000000..e37a916a1 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/about.mdx @@ -0,0 +1,53 @@ +--- +id: about +title: Overview +slug: / +description: 'Tolgee SDK provides the functionality of an i18n library and helps you with formatting, interpolation, or language detection. ' +--- + +import { ScreenRecording } from '../../platform/shared/_ScreenRecording'; + +Tolgee SDK provides the functionality of an i18n library and helps you with [formatting](./formatting), interpolation, and [language detection](./language#language-detection). +The SDK also allows you to connect to the [Tolgee Platform](../../platform/). It enables you to implement [in-context translation](./in-context) which also helps you generate one-click screenshots and edit translations directly in your app. + +## In-context Translation + +In-context translation helps you speed up development by saving you from tedious localization tasks. Using the Tolgee SDK, you can implement in-context translation. Learn more about in-context translation and how to implement it in your local development environment in [this documentation](./in-context). + + + + + +You can also enable in-context translation in the production version of your web application. Set up the [Tolgee Tools](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) to use in-context translation in production. + + + + + +## Automated Screenshots Generation + +To help translators understand the exact context of the translation, you can now add screenshots. Generate one-click screenshots with in-context translation or the [Tolgee Tools](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) extension. + + + + + +## Automatic Context Collection + +The SDK automatically collects additional context about the layout of the keys on the page. This context allows the Tolgee Translator to provide you with better results, utilizing ChatGPT under the hood. + +### What data are collected and when? + +Every time you save a translation through the in-context translation dialog box, the SDK sends a list of keys currently present on the page to the Tolgee platform. The platform then tracks the relationships between the keys based on thier order of appreance across different pages. + +### Is this safe, and what about data leaks? + +We only collect information about the order of the translations, which are already present on the platform. This order information helps the model better understand their relationships. We do not collect any additional information from your page. + +## Review translations + +Your team can review the translations directly in your app, make any necessary changes, and mark these translations as reviewed. This helps you set up a workflow for translation, speeding up the process and resulting in faster development. + + + + diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/about.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/about.mdx new file mode 100644 index 000000000..b1b0f90e2 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/about.mdx @@ -0,0 +1,15 @@ +--- +id: about +title: About +description: "Tolgee core package is platform agnosting and aims to provide universal basis for all other packages from Tolgee SDK." +--- + +## Tolgee core + +Tolgee core package is platform agnosting and aims to provide universal basis for all other packages from Tolgee SDK. + +It exports these basic parts: + +- [`TolgeeCore`](./tolgee#tolgeecore) instance creator +- [`FormatSimple`](./format-simple) plugin +- [Other tools](./other-tools) diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/events.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/events.mdx new file mode 100644 index 000000000..2d5c5516b --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/events.mdx @@ -0,0 +1,84 @@ +--- +id: events +title: Core Events +sidebar_label: Events +--- + +Tolgee events which can be listened through `tolgee.on` method. + +### language + +Emitted on language change. + +```ts +tolgee.on('language', handler: ListenerHandler) +``` + +### pendingLanguage + +Emitted on language change. Before languages are loaded (when tolgee is running). + +```ts +tolgee.on('pendingLanguage', handler: ListenerHandler) +``` + +### loading + +Emitted on loading change. Changes when tolgee is loading some data for the first time. + +```ts +tolgee.on('loading', handler: ListenerHandler) +``` + +### fetching + +Emitted on fetching change. Changes when tolgee is fetching any data. + +```ts +tolgee.on('fetching', handler: ListenerHandler) +``` + +### initialLoad + +Emitted when `run` method finishes. + +```ts +tolgee.on('initialLoad', handler: ListenerHandler) +``` + +### running + +Emitted when internal `running` state changes. + +```ts +tolgee.on('initialLoad', handler: ListenerHandler) +``` + +### cache + +Emitted when cache changes. + +```ts +tolgee.on('cache', handler: ListenerHandler) +``` + +### update + +Emitted when any key needs (or might need) to be re-rendered. Similar to `tolgee.onNsUpdate`, +except we intercept all events, not just selection. + +```ts +tolgee.on('update', handler: ListenerHandler) +``` + +### error + +Emitted when there is an error. You can intercept different types of errors, connected to fetching language data, detecting language or loading/storing language, you can filter them by `name` property on the error, which can be: + - `RecordFetchError` - error when fetching translations record, you can also read `language` and `namespace` properties, to see which record has failed + - `LanguageDetectorError` - error when detecting language through language detector plugin + - `LanguageStorageError` - error when loading/saving language through language storage plugin + +```ts +tolgee.on('error', handler: ListenerHandler) +``` + diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/format_simple.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/format_simple.mdx new file mode 100644 index 000000000..69eda8736 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/format_simple.mdx @@ -0,0 +1,47 @@ +--- +id: format_simple +title: FormatSimple +sidebar_label: FormatSimple +slug: format-simple +description: Super light formatter, which will enable you to pass variables into translations. It is a subset of ICU format. +--- + +Super light formatter, which will enable you to pass variables into translations. +It's a subset of Icu format. + +## Usage + +```ts +import { FormatSimple } from '@tolgee/core'; + +const tolgee = Tolgee() + .use(FormatSimple()) + .init(...) +``` + +## Syntax + +Passing variable: + +```ts +f('Martin is {age} years old.', { age: 10 }); +// Martin is 10 years old +``` + +You can also escape special characters by wrapping them into `'`, it works the same as in Icu standard and it only +acts like escaping character when it's followed by escapable character ("{`). It doesn't have to be closed (then all +rest of the translation is escaped). + +```ts +f("This is escaped text: '{age}'"); +// This is escaped text: {age} + +f("This is also '{fine}"); +// This is also {fine} + +f("This will work 'as expected'"); +// This will work 'as expected' +``` + +That's it! We've tried to do this format as simple as possible so the parser doesn't bloat your bundle. +If you need advanced features use [`@tolgee/format-icu`](/platform/translation_process/icu_message_format) package. diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/observer_options.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/observer_options.mdx new file mode 100644 index 000000000..42ccc984c --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/observer_options.mdx @@ -0,0 +1,138 @@ +--- +id: observer_options +title: Observer options +sidebar_label: Observer options +slug: observer-options +--- + +Tolgee Observer options which are part of [tolgee options](./options). Allows you to influence `ObserverPlugin` if present. + +### targetElement + +Element where wrapped strings are expected in development mode. + +```ts +targetElement: Node; +``` + +Default: `document` + +### fullKeyEncode + +Encode full key info into the wrapped translation (read more about [wrapping](/js-sdk/wrapping)). This option allows you to wrap translations on the server and then unwrap on the client. The option is important for the wrapping side (server), the unwrapping side can handle both cases. + +> By default Tolgee encodes only key indexes and that requires the usage of the same instance for wrapping and unwrapping. + +Default: `false` + + +### tagAttributes + +Tolgee is able to watch also for wrapped localization strings in attributes of DOM elements. +These attributes must be specified in these configuration properties. + +```ts +tagAttributes: Record; +``` + +Default: + +```ts +tagAttributes: { + textarea: ['placeholder'], + input: ['value', 'placeholder'], + img: ['alt'], + '*': ['aria-label', 'title'], +} +``` + +### highlightKeys + +By default, in development mode, when you move mouse over an element containing translated text and key ALT is down, this element is highlighted by changing its background color. To modify the key use highlightKeys property. + +```ts +highlightKeys: ModifierKey[]; +``` + +Example: + +```ts +import { ModifierKey } from '../clients/js/packages/core/lib/index'; + +highlightKeys: [ModifierKey.Shift, ModifierKey.Alt]; +``` + +Default: `[ModifierKey.Alt]` + +### highlightColor + +Highlighter border color. + +```ts +highlightColor: string; +``` + +Default: `rgb(255 0 0)`  
+ +### highlightWidth + +Border width of highlighter. + +```ts +highlightWidth: number; +``` + +Default: `5px` + +### restrictedElements + +Array of elements in which you don't want Tolgee to replace wrapped strings. + +```ts +restrictedElements: string[]; +``` + +Default: `['script', 'style']` + +### inputPrefix and inputSuffix + +Only for `TextObserver` + +In development mode, strings to be translated are wrapped by `@tolgee/core` library at first and then parsed and replaced with +translated value. This mechanism is called [wrapping](/wrapping.mdx). + +`inputPrefix` is inserted before the encoded string and `inputSuffix` is inserted after the string. By those 2 strings +Tolgee recognises strings, which are meant to be translated, so its good idea to make them unique enough not to collide +with any other strings, which can appear in DOM. + +Example: + +```typescript +inputPrefix = '%-%tolgee:'; +inputSuffix = '%-%'; +``` + +These strings are unique enough to not clash with any other strings in your DOM, so it will not break your document. + +```ts +inputPrefix: string; +``` + +### passToParent + +There are elements which can contain wrapped string to be translated, but user is not able to click on them. For example +an option of `select` HTML element cannot be used for capturing click even with `ALT` down. For these reasons you can +configure Tolgee to "pass" these strings to parent and listen for click events on the parent. + +```ts +passToParent: (keyof HTMLElementTagNameMap)[] | ((node: Element) => boolean); +``` + +Default: `["option", "optgroup"]` \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/options.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/options.mdx new file mode 100644 index 000000000..cfb4c997d --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/options.mdx @@ -0,0 +1,163 @@ +--- +id: options +title: Core Options +sidebar_label: Options +--- + +Initial configuration object of tolgee. + +### language + +Initial language, you need to specify this, unless there is LanguageDetector plugin present. + +```ts +language: string; +``` + +### defaultLanguage + +Used when LanguageDetector plugin fails. + +```ts +defaultLanguage: string; +``` + +### fallbackLanguage + +A language(s) that is used when no translation is available for the current one. + +```ts +fallbackLanguage: string | string[] | Object; +``` + +You can also specify which language should fallback to which individually: + +```ts +fallbackLanguage: { + 'en-GB': 'en', + 'en-IN': ['en-GB', 'en'], + 'es-MX': 'es', +} +``` + +All the fallback languages will be fetched at the start of the application. + +### apiUrl + +Tolgee instance url (e.g. https://app.tolgee.io) + +```ts +apiUrl: string; +``` + +### apiKey + +Project API key (PAK) or Personal Access Token (PAT) + +```ts +apiKey: string; +``` + +### projectId + +Project id is necessary if you are using PAT + +```ts +projectId: number | string; +``` + +### availableLanguages + +Languages which can be used for language detection +and also limits which values can be stored. Is derrived from `staticData` keys if not provided. + +```ts +availableLanguages: string[]; +``` + +### ns + +Namespaces which should be always fetched + +```ts +ns: string[]; +``` + +### fallbackNs + +Namespaces to search when translation is not found. + +```ts +fallbackNs: FallbackNs; +``` + +### defaultNs + +Default namespace when no namespace defined (default: empty string) + +```ts +defaultNs: string; +``` + +### staticData + +These data go directly to cache or you can specify async +function which will be used to get the data. Use `:` to add namespace: + +```ts +staticData: TolgeeStaticData; +``` + +Example: + +```ts +{ + 'locale': { + 'key': 'translation' + }, + // or + 'locale': () => fetchTranslations(), + // or + 'locale:namespace': ... +} +``` + +### onFormatError + +Defines what gets displayed when formatter throws an error. (Default: "invalid") + +```ts +onFormatError: string +onFormatError(error: string, info: TranslationInfo): string +``` + +### onTranslationMissing + +Is called every time translation is missing. +If no orEmpty or defaultValue are defined, return value is rendered. (function is called regardless) + +```ts +onTranslationMissing(info: TranslationInfo): string +``` + +### tagNewKeys + +Specify tags that will be preselected for non-existent keys + +```ts +tagNewKeys: string[] +``` + + +### observerType + +Switches between invisible and text observer if present. (Default: "invisible") + +```ts +observerType: 'invisible' | 'text'; +``` + +### observerOptions + +[Observer options](./observer-options) object. + diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/other_tools.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/other_tools.mdx new file mode 100644 index 000000000..b6ec59af3 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/other_tools.mdx @@ -0,0 +1,38 @@ +--- +id: other_tools +title: Other tools +slug: other-tools +--- + +Other helper functions exported in `@tolgee/core`. + +### getTranslateProps + +Takes arguments of `t` function and returns them in standard object. +Use this if you need to wrap `t` function and use it's arguments. + +Example: + +```ts +function myT(...args) { + const props = getTranslateProps(...args); + console.log(props.key); + return tolgee.t(props); +} +``` + +### getFallback + +Used for parameters that can be single value or array or empty. (e.g. `ns`). + +```ts +getFallback(value: FallbackGeneral): string[] | undefined +``` + +### getFallbackArray + +Same as getFallback, but always returns array. + +```ts +getFallbackArray(value: FallbackGeneral): string[] +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/plugin.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/plugin.mdx new file mode 100644 index 000000000..90eeaf2d5 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/plugin.mdx @@ -0,0 +1,157 @@ +--- +id: plugin +title: Plugin +slug: plugin-api +description: Plugin is a function which receives tolgee instance and additional `tools` object, where you have access to additional methods for adding different middlewares. +--- + +## TolgeePlugin interface + +Plugin is a function which receives Tolgee instance and additional `tools` object, where you have access +to additional methods for adding different middlewares, it also needs to return tolgee instance at the end. + +```ts +function (tolgee: TolgeeInstance, tools: PluginTools): TolgeeInstance +``` + +## PluginTools + +Tools to manipulate different middleware interfaces. + +### Formatter + +#### Methods + +```ts +tools.setFinalFormatter(formatter: FormatterMiddleware): void; +tools.addFormatter(formatter: FormatterMiddleware): void; +``` + +#### Inteface + +```ts +interface FormatterMiddleware { + format(props: { + translation: string; + language: string; + params: Record; + }): string; +} +``` + +Formats translation, allows you to add custom formatter. +There can be multiple formatters, they will be applied in order in which they were added. +`FinalFormatter` is applied last and can return different value than string. + +### Observer + +#### Methods + +```ts +tools.setObserver(observer: ObserverMiddleware): void; +tools.hasObserver(): boolean; +``` + +#### Interface + +```ts +type ObserverMiddleware = (props: ObserverProps) => { + unwrap: (text: string) => Unwrapped; + wrap: WrapperWrapFunction; + retranslate: () => void; + stop: () => void; + run: (props: ObserverRunProps) => void; + highlight: HighlightInterface; + outputNotFormattable: boolean; +}; +``` + +Middleware which enables in-context translating. Check [Observer options](./observer-options). + +### UI + +#### Methods + +```ts +tools.setUi(ui: UiMiddleware): void; +tools.hasUi(): boolean; +``` + +Tolgee UI enables in-context translating. + +### Backend + +#### Methods + +```ts +tools.addBackend(backend: BackendMiddleware): void; +tools.setDevBackend(backend: BackendMiddleware): void; +``` + +#### Interface + +```ts +interface BackendMiddleware { + getRecord(props: { + language: string; + namespace?: string; + }): Promise; +} +``` + +`Backend` middleware is used when `Tolgee` needs to fetch record (identified by language and namespace). +`Backend` can be skipped if `getRecord` returns `undefined`, in that case next backend is used. + +`DevBackend` is used when in dev mode and only difference in interface is that `getRecord` will also +recieve `apiUrl`, `apiKey` and `projectId`. It's used to connect to Tolgee platform. + +### Language detector + +```ts +tools.setLanguageDetector( + languageDetector: LanguageDetectorMiddleware +): void +``` + +#### Interface + +```ts +interface LanguageDetectorMiddleware { + getLanguage: (props: { + availableLanguages: string[]; + }): string | Promise; +} +``` + +Language detector, use for detection from user locale/cookies, etc. + +### Language storage + +```ts +tools.setLanguageStorage( + languageStorage: LanguageStorageMiddleware +): void +``` + +#### Interface + +```ts +interface LanguageStorageMiddleware { + getLanguage(): strin | Promise; + setLanguage(language: string): void | Promise; +} +``` + +Language storage, use for storing language in LocalStorage or on custom server. + +### Override credentials + +```ts +tools.overrideCredentials(credentials: { + apiUrl?: string; + apiKey?: string; + projectId?: string | number; +}): void; +``` + +Method which overrides user credentials to tolgee platform. diff --git a/js-sdk_versioned_docs/version-5.x.x/api/core_package/tolgee.mdx b/js-sdk_versioned_docs/version-5.x.x/api/core_package/tolgee.mdx new file mode 100644 index 000000000..eb8e52b91 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/core_package/tolgee.mdx @@ -0,0 +1,434 @@ +--- +id: tolgee +title: Tolgee +description: TolgeeCore allows you to gradually initialize Tolgee instance. You can repeatedly call `use` and `updateDefaults`. +--- + +## TolgeeCore + +Allows you to gradually initialize Tolgee instance. You can repeatedly call `use` and `updateDefaults`. Finally when you call `init` it will apply everything at once and return `TolgeeInstance`. + +```ts +function TolgeeCore(): TolgeeChainer; +``` + +Basic usage: + +```ts +import { TolgeeCore } from '@tolgee/core'; + +const tolgee = TolgeeCore() + .use(...) + .updateDefaults(...) + .init(...); +``` + +:::info +Constructor called [`Tolgee`](../web_package/plugins#tolgee) is located in `@tolgee/web` package as it automatically includes browser-specific features, which can't be included directly in the core. Use that if you run Tolgee in the browser. +::: + +### use + +Add tolgee plugin. + +```ts +TolgeeCore().use(plugin: TolgeePlugin): TolgeeChainer +``` + +Plugins are applied after `init` is called. To see how TolgeePlugin interface work check [Plugin API](./plugin-api). + +### updateDefaults + +Updates [Tolgee options](./options) before `init` is called. Extends existing options, +so it only changes the fields, that are listed. + +```ts +TolgeeCore().updateDefaults(options: TolgeeOptions): TolgeeChainer +``` + +You can call it multiple times and rewrite only part of the configuration: + +```ts +const tolgee = TolgeeCore() + .updateDefaults({ apiKey: 'key' }) + .updateDefaults({ language: 'en' }) + .init({ apiUrl: 'url' }); +// resulting configuration will be: +{ + apiUrl: 'url', + apiKey: 'key', + language: 'en' +} +``` + +## TolgeeInstance + +Represents initialized tolgee instance. Allows you to read translations and manipulate lanugage or cache. + +### run + +Changes internal state to `running: true` and loads initial files. Starts `Observer` if present. + +```ts +tolgee.run(): Promise +``` + +### stop + +Changes internal state to `running: false` and stops runnable plugins. + +```ts +tolgee.stop(): void +``` + +```ts +tolgee.init(options: TolgeeOptions): TolgeeInstance +``` + +### on + +Allows you to listen to tolgee events. + +```ts +tolgee.on(event: TolgeeEvent, handler: ListenerHandler): Listener +``` + +Returned object has `tolgee.unsubscribe`, call it to stop listening. + +```ts +const listner = tolgee.on(...) +listener.unsubscribe() +``` + +Check [Events](./events) for details. + +### onNsUpdate + +Allows you to listen to key changes of namespaces that you pick. + +```ts +tolgee.onNsUpdate(handler: ListenerHandler): ListenerSelective +``` + +Returns object, which allows you to subscribe to namespaces. + +```ts +const listener = tolgee.onNsUpdate(...) +// subscribes namespace +listener.subscribeNs(ns) +// unsubscribe completely +listener.unsubscribe() +``` + +Use this when you want to make sure, you always update translation when it's necessary. +It internally stores list of namespaces that you are interested in and calls the handler, when necessary. + +### t + +Returns formatted translation based on current language. If `Observer` is present and tolgee is running, `wraps` the result to be identifiable in the DOM. + +```ts +tolgee.t(key: string, defaultValue?: string, options?: CombinedOptions): string +tolgee.t(key: string, options?: CombinedOptions): string +tolgee.t(props: TranslateProps): string +``` + +`t` function provides flexibility in how to pass the options: + +```ts +// with arguments +const translation = t('key', 'default value', { + ns: 'test', + // any unknown property in CombinedOptions is taken as param + parameter: 'parameter', +}); +// with props object +const translation = t({ + key: 'key', + defaultValue: 'default value', + ns: 'test', + params: { parameter: 'parameter' }, +}); +``` + +All the options: + +- `key`: `string` - translation key +- `defaultValue`: `string` - value displayed when no translation is available +- `noWrap`: `boolean` - returns translation without wrapping (default: false) +- `orEmpty`: `boolean` - return empty string instead of key if translation doesn't exist (default: false) +- `params`: `Record` object with parameters for the formatter +- `ns`: `string` - namespace, if not provided default namespace is used +- `language`: `string` - override current Tolgee language. This way you can override current language and use different one from the cache. **Language needs to be loaded manually by [`tolgee.loadRecord`](/api/core_package/tolgee.mdx#loadrecord)**. + +> `t` function serves only for reading, not for loading new namespaces or languages. Use [`addActiveNs`](#addactivens) for loading additional namespaces. + +### changeLanguage + +Change current language. + +```ts +tolgee.changeLanguage(language: string): Promise +``` + +- if tolgee is not running sets `pendingLanguage`, `language` to the new value +- if tolgee is running sets `pendingLanguage` to the value, fetches necessary data and then changes `language` + +Returns promise which is resolved when `language` is changed. + +### getLanguage + +Returns current `language` if set. + +```ts +tolgee.getLanguage(): string | undefined +``` + +### getPendingLanguage + +Returns current `pendingLanguage` if set. + +```ts +tolgee.getPendingLanguage(): string | undefined +``` + +`pendingLanguage` represents language which is currently being loaded. + +### addActiveNs + +Adds namespace(s) list of active namespaces and if tolgee is `running`, loads required data. Active namespaces are also loaded automatically when language is changed. + +```ts +tolgee.addActiveNs(ns: FallbackNsTranslation, forget?: boolean): Promise +``` + +- `ns`: `string` | `string[]` - namespace(s) +- `forget`: `boolean` - if true, tolgee only fetches the data, but won't remember namespaces as active, so it won't get loaded automatically on language change + +### removeActiveNs + +Remove namespace(s) from active namespaces. + +```ts +tolgee.removeActiveNs(ns: FallbackNsTranslation): void +``` + +Tolgee internally counts how many times was each active namespace added, +so this method will remove namespace only if the counter goes down to 0. + +```ts +tolgee.addActiveNs('common'); +tolgee.addActiveNs(['common', 'component']); +// active namespaces: {common: 2, component: 1} +tolgee.removeActiveNs(['common', 'component']); +// active namespaces: {common: 1} +tolgee.removeActiveNs('common'); +// active namespaces: {} +``` + +This is used in component lifecycles, where we want to keep track which namespaces are needed, when language changes. + +### isRunning + +Returns `true` if tolgee is running. + +```ts +tolgee.isRunning(): boolean +``` + +### isInitialLoading + +Returns `true` if tolgee is loading initial data (triggered by `run`). + +```ts +tolgee.isInitialLoading(): boolean +``` + +### isFetching + +Returns `true` if tolgee is fetching any data. + +```ts +tolgee.isFetching(ns?: FallbackNsTranslation): boolean +``` + +- `ns`: `string` | `string[]` - optional list of namespaces that you are interested in + +### isLoading + +Returns `true` if tolgee is loading initial data or when new namespace is added. When you are changing language +this won't get triggered. Basically indicates, that Tolgee doesn't have all the neccessary data available. + +```ts +tolgee.isLoading(ns?: FallbackNsTranslation): boolean +``` + +- `ns`: `string` | `string[]` - optional list of namespaces that you are interested in + +### isLoaded + +Similar to `isLoading`, but can be called before the actual loading starts. This function is mostly useful +when you need to find out if tolgee has all the necessary data before `run` function is triggered. + +```ts +tolgee.isLoaded(ns?: FallbackNsTranslation): boolean +``` + +### getRequiredRecords + +Returns records needed for instance to be `loaded` + +```ts +tolgee.getRequiredRecords(): CacheDescriptor[] +``` + +- `ns`: `string` | `string[]` - optional list of namespaces that you are interested in + +### isDev + +Returns `true` if tolgee is in dev mode. +Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` is specified. + +```ts +tolgee.isDev(): boolean +``` + +### setEmitterActive + +Turn off/on events emitting. Is on by default. + +```ts +tolgee.setEmitterActive(active: boolean) +``` + +### getInitialOptions + +Returns current [Tolgee options](./options). + +```ts +tolgee.getInitialOptions(): TolgeeOptions +``` + +### highlight + +Highlights keys that match selection. + +```ts +tolgee.highlight(key?: string, ns?: FallbackNsTranslation): Highlighter +``` + +Returns object with `unhighlight` method. + +### wrap + +Wraps translation if there is `Observer plugin`. + +```ts +tolgee.wrap(params: TranslatePropsInternal): string | undefined +``` + +### unwrap + +Returns wrapped translation info. + +```ts +tolgee.unwrap(text: string): Unwrapped +``` + +### loadRecord + +Manually load record from `Backend` (or `DevBackend` when in dev mode). + +```ts +tolgee.loadRecord(descriptors: CacheDescriptor): Promise +``` + +### loadRecords + +Manually load multiple records from `Backend` (or `DevBackend` when in dev mode). + +```ts +tolgee.loadRecords(descriptors: CacheDescriptor[]): Promise +``` + +It loads data together and adds them to cache in one operation, to prevent partly loaded state. + +### getRecord + +Get record from cache. + +```ts +tolgee.getRecord(descriptors: CacheDescriptor[]): TranslationsFlat | undefined +``` + +### getAllRecords + +Get all records from cache. + +```ts +tolgee.getAllRecords(): CachePublicRecord[] +``` + +### addStaticData + +Add data to cache. It will only rewrite cache if there are no dev data loaded. + +Example: + +```ts +tolgee.addStaticData({ + 'locale': { + 'key': 'translation' + }, + // or + 'locale': () => fetchTranslations(), + // or + 'locale:namespace': ... +}) +``` + +```ts +tolgee.addStaticData(data: TolgeeStaticData): CachePublicRecord[] +``` + +### changeTranslation + +Temporarily change translation in cache. + +```ts +tolgee.changeTranslation(descriptor: CacheDescriptor, key: string, value: string): TranslationChanger +``` + +Returns object with `revert` method. + +### overrideCredentials + +Override creadentials passed on initialization. When called in running state, tolgee stops and runs again. + +```ts +tolgee.overrideCredentials(credentials: DevCredentials) +``` + +```ts +type DevCredentials = { + apiUrl?: string; + apiKey?: string; + projectId?: string | number; +}; +``` + +### addPlugin + +Add tolgee plugin after initialization. When called in running state, tolgee stops and runs again. To see how TolgeePlugin interface work check [Plugin API](./plugin-api). + +```ts +tolgee.addPlugin(plugin: TolgeePlugin | undefined) +``` + +### updateOptions + +Updates [Tolgee options](./options) after instance creation. Extends existing options, so it only changes the fields, that are listed. When called in `running` state, tolgee stops and runs again. + +```ts +tolgee.updateOptions(options?: TolgeeOptions) +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/api/packages.mdx b/js-sdk_versioned_docs/version-5.x.x/api/packages.mdx new file mode 100644 index 000000000..c0173c9dd --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/packages.mdx @@ -0,0 +1,32 @@ +--- +id: packages +title: Packages +sidebar_label: Packages +slug: /sdk/packages +--- + +Tolgee SDK is split into several npm packages. + +## Dependency graph + +![img](/img/docs/web/packages.svg) + +### @tolgee/core + +Main `Tolgee` module, which is highly extensible with plugins. Its main purpose is to manage the translations cache +and provide access to it through the `t` method (which returns translation by key). It also allows you to listen +to language changes, translation changes, cache changes etc. + +### @tolgee/web + +Extends `@tolgee/core` with web-specific modules. Mainly it contains `InContextTools`/`DevTools` plugins. +These plugins allow you to connect to `tolgee platform` and edit your translations directly in your app +(read more in [In-context translating](/in_context.mdx)). + +### Integration libraries + +Provide framework/library specific wrappers around `Tolgee`. + +### @tolgee/format-icu + +Provides plugin for [Icu message format](/platform/translation_process/icu_message_format). diff --git a/js-sdk_versioned_docs/version-5.x.x/api/web_package/about.mdx b/js-sdk_versioned_docs/version-5.x.x/api/web_package/about.mdx new file mode 100644 index 000000000..dc082a059 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/web_package/about.mdx @@ -0,0 +1,29 @@ +--- +id: about +title: About +sidebar_label: About +--- + +## Tolgee web + +Tolgee web package contains all general web-related tools. Mainly connected to in-context translating as it is tightly coupled to the DOM and web environment. Tolgee exports everything from `@tolgee/core`, so you can just install this package if you use Tolgee on the web. + +It contains these basic parts: + +- [`Tolgee`](./plugins#tolgee) +- [`LanguageStorage`](./plugins#languagestorage) +- [`LanguageDetector`](./plugins#languagedetector) +- [`BackendFetch`](./plugins#backendfetch) +- [`DevTools`](./tools#devtools) +- [`DevBackend`](./plugins#devbackend) +- [`ObserverPlugin`](./plugins#observerplugin) +- [`BrowserExtensionPlugin`](./plugins#browserextensionplugin) +- [Constants](./constants) +- \+ everything from [`@tolgee/core`](../core_package/about) + +Plus in-context tools: + +- [`InContextTools`](./tools#incontexttools) +- [`ContextUi`](./tools#contextui) + + diff --git a/js-sdk_versioned_docs/version-5.x.x/api/web_package/constants.mdx b/js-sdk_versioned_docs/version-5.x.x/api/web_package/constants.mdx new file mode 100644 index 000000000..bcdd8a602 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/web_package/constants.mdx @@ -0,0 +1,19 @@ +--- +id: constants +title: Constants +slug: constants +--- + +```ts +// name of attribute, which is used on html elements +// that are active for user to click in-context +const TOLGEE_ATTRIBUTE_NAME = '_tolgee'; + +// use this attribute on any html element with translation key +// and it will be in-context clickable +const TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = 'data-tolgee-key-only'; + +// use this attribute when you want to disable tolgee observer +// on html element and all it's children +const TOLGEE_RESTRICT_ATTRIBUTE = 'data-tolgee-restricted'; +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/api/web_package/plugins.mdx b/js-sdk_versioned_docs/version-5.x.x/api/web_package/plugins.mdx new file mode 100644 index 000000000..56b751898 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/web_package/plugins.mdx @@ -0,0 +1,110 @@ +--- +id: plugins +title: Plugins +sidebar_label: Plugins +--- + +## Tolgee + +[`TolgeeCore`](../core_package/tolgee#tolgeecore) with added [`BrowserExtensionPlugin`](./plugins#browserextensionplugin). + +Usage: + +```ts +const tolgee = Tolgee() + .use(...) + .updateDefaults(...) + .init(...) +``` + +## `LanguageStorage` + +Plugin for storing current language in `localStorage`. It remembers the language if the user changed it. + +```ts +tolgee.use(LanguageStorage()); +``` + +:::info +Don't use [`language`](https://tolgee.io/js-sdk/api/core_package/options#language) property with this plugin, as it will hard-override the languge. Instead use [`defaultLanguage`](https://tolgee.io/js-sdk/api/core_package/options#defaultlanguage). +::: + +## `LanguageDetector` + +Plugin for language detection from the user's browser locale. It requires `staticData` or `availableLanguages` to be defined in Tolgee options, as it matches the locale with it. + +```ts +tolgee.use(LanguageDetector()); +``` + +:::info +Don't use [`language`](https://tolgee.io/js-sdk/api/core_package/options#language) property with this plugin, as it will hard-override the languge. Instead use [`defaultLanguage`](https://tolgee.io/js-sdk/api/core_package/options#defaultlanguage). +::: + +## `BackendFetch` + +Plugin for fetching translations JSON files. + +```ts +tolgee.use(BackendFetch(options?: BackendOptions)); +``` + +Pass `BackendOptions` to customize the behavior: + +### `options.prefix` + +Pass string to change URL prefix, use can use relative or absolute paths (Default: `/i18n`) + +### `options.getPath` + +Combines prefix, namespace and language and generates file path. + +```ts +function ({ prefix, namespace, language }): string +``` + +Default returns `{prefix}/{namespace}/{language}.json` or `{prefix}/{language}.json` when namespace is empty. + +### `options.getData` + +Parses data from response, by default it returns `r.json()`. + +```ts +function (r: Response): Promise; +``` + +### `options.headers` + +Pass custom headers. Default: `{ Accept: 'application/json' }` + +## `DevTools` + +It's a combination [`ObserverPlugin`](./plugins#observerplugin), [`ContextUi`](./tools#contextui) and [`DevBackend`](./plugins#devbackend) in one plugin, intended to enable in-context capabilities. It only applies plugins if there they are not already applied. + +`DevTools` are automatically omitted in production builds (based on `NODE_ENV` variable). + +If you need include in-context tools in your production build check [`InContextTools`](/api/web_package/tools.mdx#incontexttools), which are completely equivalent to `DevTools`, only without auto-omitting logic. + + +## `DevBackend` + +Plugin for communication with Tolgee platform uses `apiUrl`, `apiKey` (+ `projectId`) from Tolgee options. + +```ts +tolgee.use(DevBackend()); +``` + +## `ObserverPlugin` + +Plugin which wraps translations and observes the DOM, so it's able to locate translations on the page. Read more in [this article](/blog/2021/12/17/invisible-characters-for-better-localization). + +```ts +tolgee.use(ObserverPlugin()); +``` + +You can influence the behavior of this plugin through [`observer options`](../core_package/observer-options). + + +## `BrowserExtensionPlugin` + +Plugin which connects Tolgee to [Tolgee Tools browser extension](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj). This plugin is automatically included in Tolgee, if you import it from `@tolgee/web`. diff --git a/js-sdk_versioned_docs/version-5.x.x/api/web_package/tools.mdx b/js-sdk_versioned_docs/version-5.x.x/api/web_package/tools.mdx new file mode 100644 index 000000000..820ca9286 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/api/web_package/tools.mdx @@ -0,0 +1,50 @@ +--- +id: tools +title: Tools +sidebar_label: Tools +slug: tools +--- + + +As `DevTools` are automatically omitted on production, `@tolgee/web/tools` can be used to include it unconditionally. + +:::info +Tools were moved from `@tolgee/web` in version 5.2.0. +::: + +## `InContextTools` + + +Similar to [`DevTools`](./plugins#devtools). Use `InContextTools` only if you know what you are doing. + +It's a combination [`ObserverPlugin`](./plugins#observerplugin), [`ContextUi`](./tools#contextui) and [`DevBackend`](./plugins#devbackend) in one plugin, which you can use when you want to enable in-context capability anywhere. It only applies plugins if there they are not already applied. You can optionally provide credentials to Tolgee platform, which will override anything that was set before. + +This plugin is automatically downloaded and applied by [`BrowserExtensionPlugin`](./plugins#browserextensionplugin) when you enable in-context translating. + +```ts +import { InContextTools } from '@tolgee/web/tools' + +tolgee.use(InContextTools(props?: InContextOptions)); +``` + +```ts +type InContextOptions = { + credentials: { + apiUrl?: string; + apiKey?: string; + projectId?: string | number; + }; +}; +``` + +Can be applied even when `Tolgee` is already running. + +## `ContextUi` + +Tolgee in-context modal, which allows you to edit translation in-context. + +```ts +import { ContextUi } from '@tolgee/web/tools' + +tolgee.use(ContextUi()); +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/filter_by_tags.mdx b/js-sdk_versioned_docs/version-5.x.x/filter_by_tags.mdx new file mode 100644 index 000000000..4b473a6eb --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/filter_by_tags.mdx @@ -0,0 +1,17 @@ +--- +id: filter_by_tags +title: Filter keys by tags +description: '' +--- + +You can configure Tolgee to only use keys with certain tags. This can be useful if you are sharing translations e.g. between multiple platforms (e.g. web and mobile). + +```ts +const tolgee = Tolgee() + .init({ + filterTag: ['web'] + ... + }) +``` + +With this setup, Tolgee SDK will only load keys with the given tag. If you specify multiple tags, keys with any of the specified tags will be loaded. \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/formatting.mdx b/js-sdk_versioned_docs/version-5.x.x/formatting.mdx new file mode 100644 index 000000000..457b14125 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/formatting.mdx @@ -0,0 +1,56 @@ +--- +id: formatting +title: Formatting +description: 'Learn how to use in-context translating feature in Tolgee tool and how to enable in-context translating on production.' +--- + +Formating is a way how to render translation into a final string, with variable parameters. Formatters are supported in form of [plugin](./plugins.mdx). + +## Simple formatter + +[`FormatSimple`](./api/core_package/format-simple) plugin if you just need to include variables in your translations. It is very minimalistic, only intended to interpolate your parameters and save your bundle size. + +```ts +import { FormatSimple } from '@tolgee/web'; +... +tolgee.use(FormatSimple()); +``` + +### Example + +```ts +tolgee.t('basket_items', '{count} items in the basket', { count: 10 }) +// -> 10 items in the basket +``` + +## Icu formatter + +For advanced formatting functionality install `FormatIcu` plugin, which is shipped in separate package. + +import { InstallationTabs } from '../../src/component/InstallationTabs'; + + + +Use it like this: + +```ts +import { FormatIcu } from '@tolgee/format-icu'; + +tolgee.use(FormatIcu()); +``` + +### Examples + +```ts +tolgee.t('progress', 'Progress: {progress, number, percent}', { progress: 0.1 }) +// -> Progress: 10% + +tolgee.t('basket_items', '{count, plural, one {# item} other {# items}} in the basket', { count: 10 }) +// -> 10 items in the basket +``` + +Read more about [ICU message format](/platform/translation_process/icu_message_format) + +:::info +For react-native you might need to polyfill `Intl` object. Use [`@formatjs/intl-locale`](https://formatjs.github.io/docs/polyfills/intl-locale) and [`@formatjs/intl-pluralrules`](https://formatjs.github.io/docs/polyfills/intl-pluralrules) to make plurals work. +::: diff --git a/js-sdk_versioned_docs/version-5.x.x/get_started.mdx b/js-sdk_versioned_docs/version-5.x.x/get_started.mdx new file mode 100644 index 000000000..784e5e192 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/get_started.mdx @@ -0,0 +1,20 @@ +--- +id: get_started +title: Get started +description: 'Learn how to get started with the Tolgee SDK for seamless localization. Use framework-specific SDKs or the vanilla JavaScript SDK to easily integrate localization into your application.' +--- + +Tolgee SDK supports multiple frameworks and libraries. These specific SDKs are all based on the [`@tolgee/web`](./api/web_package/about) package. Tolgee also integrates seamlessly with [i18next](https://www.i18next.com/). Use [Tolgee's i18next plugin](./integrations/i18next/installation) if you are already using i18next or need more advanced featured offered by i18next. You can also use Tolgee with [Vanilla JavaScript](./integrations/vanilla/installation). + +Below is a list of all the available frameworks and libraries Tolgee supports today. To get started, install the package and follow the instructions for the respective framework or library. + +## Integrations + +- [React](./integrations/react/overview) + - [Next.js](./integrations/react/next/introduction) + - [React Native](./integrations/react/react_native) +- [Angular](./integrations/angular/overview) +- [Svelte](./integrations/svelte/overview) +- [Vue](./integrations/vue/overview) +- [i18next library](./integrations/i18next/installation) +- [Vanilla JS](./integrations/vanilla/installation) diff --git a/js-sdk_versioned_docs/version-5.x.x/in_context.mdx b/js-sdk_versioned_docs/version-5.x.x/in_context.mdx new file mode 100644 index 000000000..c0eab3c88 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/in_context.mdx @@ -0,0 +1,98 @@ +--- +id: in_context +title: In-context translating +slug: in-context +description: 'Learn how to use in-context translating feature in Tolgee tool and how to enable in-context translating on production.' +--- + +For local development, supply `apiUrl`, `apiKey` options (see [Integration](/platform/integrations/about_integrations)) and [`DevTools`](./api/web_package/tools#devtools) plugin. If you set everything properly, you can use in-context translating: + +1. Press and hold `Alt`/`Option` key +2. Navigate mouse over any translation on your website +3. It should get highlighted +4. Click on it to open "Quick translation" dialog +5. You can edit translations (changes will get stored to Tolgee platform) + +> NOTE: If you use Tolgee without any integration, make sure you call [`run`](./api/core_package/tolgee.mdx#run) method. + +## One-click screenshots + +To generate screenshots automatically, you have to have installed +[Tolgee Tools](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) plugin +for Google Chrome browser. With this plugin, you can generate screenshots by clicking camera icon in Tolgee UI +translation dialog. + +:::info +[`DevTools`](./api/web_package/tools#devtools) are automatically omitted in production builds (based on `NODE_ENV` variable). +::: + +## In-context on production + +If you want to enable in-context on production build, there are two options: + +### 1. Tolgee Chrome plugin + +To enable in-context translating on production you can pass `apiKey` and `apiUrl` through [Tolgee Tools](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) plugin, that way Tolgee will switch to development mode (plugin will also supply `@tolgee/ui` package automatically). + +:::warning +Never include `apiKey` in Tolgee configuration on production. +This property is intended only for local development, as it's not meant to be publicly visible. +::: + +1. Install [Tolgee Tools](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) plugin +2. Go to the production version of your website +3. Click on Tolgee Tools extension and apply your API key (you might need to reload if you just installed the plugin) +4. In-context translating should work + +> You can also check [Step-by-step tutorial](/blog/in-context-production) + +#### A few notes about Tolgee Tools plugin + +- You can turn off development mode by switching "Applied" toggle, which will turn it off, but stores credentials locally so you don't have to fill it again next time +- Plugin will supply `apiUrl` from Tolgee configuration if it's present (if not you can fill it manually) +- Plugin changes its icon if Tolgee is present on the page or if credentials are set + +### 2. Using InContextTools instead of DevTools + +DevTools are automatically omitted in production build (based on `NODE_ENV` variable), if you want to enable In-context unconditionally, you can use [`InContextTools`](/api/web_package/tools.mdx#incontexttools) (from `@tolgee/web/tools` package). + +Note that [`InContextTools`](/api/web_package/tools.mdx#incontexttools) will significantly increase your bundle size, so we recommend using dynamic import and only including them when needed. + +:::warning +In-context on production should be always behind some authentication or in a private network. Make sure you are not exposing your credentials publicly. +::: + +```ts +// initialize tolgee without DevTools +const tolgee = Tolgee() + .use(FormatSimple()) + .init({ + ... + }); + +// include in-context tools only if needed +if (shouldIncludeInContextTools) { + import('@tolgee/web/tools').then((module) => { + tolgee.addPlugin(module.InContextTools()) + }) +} +``` + +#### Using parts of InContextTools functionality + +[`InContextTools`](/api/web_package/tools.mdx#incontexttools) (or `DevTools` in development) are a combination of multiple plugins, which you can include independently, check [`InContextTools`](/api/web_package/tools.mdx#incontexttools) documentation for more info. + +## Modals, popovers and other focus-stealing elements +Some elements like modals or popovers can steal focus from the Tolgee dev tools, +so you can't type in the translation input fields. + +Fortunately, you can still use the dev tools by opening them in a separate popup window by clicking the icon next to the `Quick translation` dialog title. + +Your browser might block the popup, so you have to allow it. + +import { ScreenshotWrapper } from '../../platform/shared/_ScreenshotWrapper'; + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/api.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/api.mdx new file mode 100644 index 000000000..5c8f2e2c3 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/api.mdx @@ -0,0 +1,187 @@ +--- +id: api +title: API +sidebar_label: API +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library for Angular. Learn about NgxTolgeeModule, TranslateService, T-component, and other APIs." +--- + +The `@tolgee/ngx` library exports the following components and all available components from [`@tolgee/web`](/api/web_package/about.mdx). + +## NgxTolgeeModule + +This is Tolgee Module. The module exports [`TranslatePipe`] and [`TComponent`]. To use Tolgee in your Angular application, you must import the `NgxTolgeeModule` in your application module. + +Example usage: + +```ts +@NgModule({ + imports: [ + NgxTolgeeModule, + ... + ], + providers: [ + { + provide: TOLGEE_INSTANCE, + ... + }, + ], + ... +}) +``` + +## TOLGEE_INSTANCE + +The injection token to provide [Tolgee instance](/api/core_package/tolgee.mdx). + +```ts +@NgModule({ + imports: [ + NgxTolgeeModule, + ... + ], + providers: [ + { + provide: TOLGEE_INSTANCE, + useFactory: () => { + return Tolgee() + .use(LanguageDetector()) + .use(FormatIcu()) + .use(DevTools()) + .init({ + staticData: { + en: () => import('../i18n/en.json'), + cs: () => import('../i18n/cs.json'), + }, + apiUrl: environment.tolgeeApiUrl, + apiKey: environment.tolgeeApiKey, + fallbackLanguage: 'en', + defaultLanguage: 'en', + }); + }, + }, + ], + ... +}) +``` + +## TranslateService + +Contains methods to translate text used by other components. + +### property `tolgee` + +The [Tolgee instance](/api/core_package/tolgee.mdx). + +### property `language` + +Returns `string` containing current language. + +```ts +this.translateService.language // "en" +``` + +### property `languageAsync` + +Returns `Observable` emitting current language. When language is changed and loaded, the new value is emitted. + +### method `changeLanguage` + +Sets current language. + +```ts +this.translateService.setLang('en'); +``` + +### method `translate` + +Returns `Observable` emitting current translation for specified parameters and current language. When the current value changes due to an event (e.g. language change), the new value gets emitted. + +```ts +this.subscription = this.translateService + .translate('this_is_a_key_with_params', { key: 'value' }, 'Default value') + .subscribe((val) => (this.translated = val)); +``` + +This method accepts the same arguments as [`Tolgee.t`](/api/core_package/tolgee.mdx#t) method. + +```ts +translate(key: string, defaultValue?: string, options?: CombinedOptions): Observable +translate(key: string, options?: CombinedOptions): Observable +translate(props: TranslateProps): Observable +``` + +### method `instant` + +Returns `string` providing current translation value depending on the current language. + +```ts +const translated = this.translateService + .instant('this_is_a_key_with_params', { key: 'value' }, 'Default value') +``` + +This method accepts the same arguments as [`Tolgee.t`](/api/core_package/tolgee.mdx#t) method. + +```ts +instant(key: string, defaultValue?: string, options?: CombinedOptions): string +instant(key: string, options?: CombinedOptions): string +instant(props: TranslateProps): string +``` + +#### parameter language + +The language to be set. + +returns `Promise` relved when language data is loaded + +### method `on` + +Listens to Tolgee events. + +#### parameter `event` + +The event to listen to + +returns `Observable` emitting when the event is triggered, providing [event-specific data](/api/core_package/events.mdx). + +Read more in [Events API](/api/core_package/events.mdx) + +### method start + +Runs the `Tolgee.run` method from the `@tolgee/core` library outside Angular's NgZone. + +### TComponent + +Component with `t` attribute selector. Replaces the content of the element with the translated value. + +- Input `key` - Key to translate +- Input `ns` - The namespace of the key +- Input `params` - Object of parameters to interpolate +- Input `default` - Default value +- Input `isHtml` - Whether the input should be treated as HTML. +- Input `noWrap` - Disable wrapping +- Input `language` - Override current Tolgee language. This way you can switch to different language for separate translation. Load the language manually with [`tolgee.loadRecord`](/api/core_package/tolgee.mdx#loadrecord). + +```html +
+``` + +### `translate` pipe + +Translates a key with specific parameters or default value. The transform method of `translate` pipe accepts the same arguments as the [`tolgee.t`](/api/core_package/tolgee.mdx#t) method. + +Example usages: + +```html +{{ 'this_key_does_not_exist' | translate:'This is default'}} +{{ 'this_is_a_key_with_params' | translate:{key: 'value', key2: 'value2'} }} +{{ 'this_is_a_key_with_params' | translate:"Default value":{key: 'value', key2: 'value2'} }} +{{ { key: 'this_is_a_key', defaultValue: 'Jeeey!' } | translate}} +``` + +## NamespaceResolver + +A resolver to load namespaces while loading lazy module. Set `data.tolgeeNamespace` property to set the namespace to load. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/html_tags.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/html_tags.mdx new file mode 100644 index 000000000..9f6ad5975 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/html_tags.mdx @@ -0,0 +1,35 @@ +--- +id: html_tags +title: Strings with HTML Tags +sidebar_label: Strings with HTML Tags +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library for Angular. Learn how to render i18n strings with HTML tags in Angular." +--- + +You can render HTML tags in translations using the [`t` attribute](./translating#t-attribute) or the [`translate` pipe](./translating#translate-pipe). + +To render a translation containing HTML tags using pipe, you must set the translated value via the `innerHTML` attribute of the wrapping component. + +Consider a translation for the key `hello_peter` with the English value `Hello Peter!` + +You can render it using the `translate` pipe as follows: +```html +
+``` + +To render a translation containing HTML tags using the `t` attribute, you must set the translated value via the `innerHTML` attribute of the wrapping component. + +```html +
+``` + +## Security + +Tolgee sanitizes HTML tags in translations. It removes all HTML tags and attributes that are not allowed. + +The HTML is automatically sanitized for both `t` component and `translate` pipe. However, if you don't expect HTML strings in your code, avoid passing the strings using innerHtml. + +```html +Hello Peter! +``` + +The above code is sanitized to: `Hello Peter!` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/installation.mdx new file mode 100644 index 000000000..8d645d6ca --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/installation.mdx @@ -0,0 +1,93 @@ +--- +id: installation +title: Installation +sidebar_label: Installation +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library for Angular. To use this library, start by installing Tolgee." +--- + +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; +import PreparingForProduction from '../../shared/_PreparingForProduction.mdx'; +import ExampleBanner from '../../shared/ExampleBanner'; + +To use Tolgee in your Angular application, you need to follow these steps: + +1. [Install the Tolgee integration library](#install-the-tolgee-integration-library) +2. [Configure the Tolgee API](#configure-the-tolgee-module) +3. [Set up the environment variables](#set-up-the-environment-variables) +4. [Preparing for production](#preparing-for-production) + +## Install the Tolgee integration library + +To install Tolgee integration library, run the following command: + + + +## Configure the Tolgee module + +Once the library is installed, you need to import and initialize the `NgxTolgeeModule` in your module file (`app.module.ts` or other). + +The updated code should look like this: + +```typescript +... +import { + DevTools, + NgxTolgeeModule, + Tolgee, + TOLGEE_INSTANCE, + FormatSimple +} from '@tolgee/ngx'; +... +@NgModule({ + declarations: [ + ... + ], + imports: [ + NgxTolgeeModule, + ... + ], + providers: [ + { + provide: TOLGEE_INSTANCE, + useFactory: () => { + return Tolgee() + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + language: 'en' + + // for development + apiUrl: environment.tolgeeApiUrl, + apiKey: environment.tolgeeApiKey, + + // for production + staticData: { + ... + } + }); + }, + }, + ], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +The above code does the following: + +- Imports the required moudle, classes, and plugins from the integration library. +- Configures the module +- Configures the Tolgee instance to use the [DevTools](../../api/web_package/plugins#devtools) and [FormatSimple](../../api/core_package/format-simple) plugins, and initializes it using the credentials. + +> You can configure more options and plugins during initialization. Learn about these other [options](../../api/core_package/options) and [Tolgee plugins](../../plugins) in their respective documentation. + +## Set up the environment variables + +Follow the instructions mentioned in the [angular application environments documentation](https://angular.io/guide/build#configuring-application-environments) to configure `apiUrl` and `apiKey`. + +## Preparing for production + + + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/namespaces.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/namespaces.mdx new file mode 100644 index 000000000..241ab94f6 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/namespaces.mdx @@ -0,0 +1,40 @@ +--- +id: namespaces +title: Namespaces +sidebar_label: Namespaces +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library. You can split your i18n data into multiple namespaces using Tolgee. Learn more about using namespaces." +--- + +Tolgee allows you to split your i18n data into multiple namespaces. When using the [`translate pipe`](./translating#translate-pipe), [`t attribute`](./translating#t-attribute), or [`translation methods`](./translating#translation-methods), Tolgee automatically loads the namespace data for you. + +Ideally, you should split translations for each module into different namespaces. For a lazy module, wait for the i18n data to be fetched before loading the lazy module. You can achieve this with `NamespaceResolver`. + +```ts +... +import { NamespaceResolver } from '@tolgee/ngx'; + +const routes: Routes = [ + ... + { + path: 'lazy', + loadChildren: () => import('./lazy/lazy.module').then((m) => m.LazyModule), + data: { tolgeeNamespace: 'my-loaded-namespace' }, + resolve: { + _namespace: NamespaceResolver, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, ...), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} +``` + +When using `NamespaceResolver`, provide `tolgeeNamespace` property to the `data` object +of your route configuration as defined in the above example. + +You can learn more in the [namespaces documentation](../../namespaces.mdx). diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/overview.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/overview.mdx new file mode 100644 index 000000000..8a54de7bd --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/overview.mdx @@ -0,0 +1,11 @@ +--- +id: overview +title: Tolgee with Angular +sidebar_label: Overview +description: "Get started with Tolgee's integration for Angular. Learn how to add localization to your Angular applications." +--- + +Tolgee provides an SDK for Angular, based on the [`@tolgee/web`](../../api/web_package/about) package. Using the SDK [@tolgee/ngx](https://www.npmjs.com/package/@tolgee/ngx), you can connect your Angular application to the Tolgee platform. This SDK enables you to implement [in-context translations](../../in-context), [language detection](../../language#language-detection), interpolation, and other advanced features. + +- Follow the [installation guide](./installation) to add the Tolgee Angular SDK to your existing Angular application. +- You can also check the [example application](https://github.com/tolgee/ngx-example) to see how to integrate Tolgee with Angular. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/switching_language.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/switching_language.mdx new file mode 100644 index 000000000..b832c5b90 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/switching_language.mdx @@ -0,0 +1,38 @@ +--- +id: switching_language +title: Switching language +sidebar_label: Switching language +slug: switching-language +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library for Angular. Learn to manage language change using the TranslationService" +--- + +To manage an event like language change, the Tolgee integration provides the [`TranslationService`](./api#translateservice). + +Initialize the `TranslationService` in your component as follows: + +```ts title="language.component.ts" +import { Component } from '@angular/core'; +import { TranslateService } from '@tolgee/ngx'; + +@Component({ + ... +}) +export class AppComponent { + constructor(public translateService: TranslateService) {} + ... +} +``` + +Next, in template use the [`changeLanguage` method](./api#method-changelanguage) and [`languageAsync` property](./api#property-languageasync) from `TranslateService` to handle the language change. + +```html title="language.component.html" + +``` + +> Learn more about language change, detection and storage on the [Language documentation page](../../language). \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/angular/translating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/translating.mdx new file mode 100644 index 000000000..6dd790983 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/angular/translating.mdx @@ -0,0 +1,126 @@ +--- +id: translating +title: Translating +sidebar_label: Translating +description: "Tolgee enables you to implement localization in your Angular application with ease. You can integrate Tolgee in your Angular application using Tolgee's integration library for Angular. Learn to impelement translation using the various methods." +--- + +import TFunctionHint from '../../shared/_TFunctionHint.mdx'; +import NamespacesHint from '../../shared/_NamespacesHint.mdx'; +import ExampleBanner from '../../shared/ExampleBanner'; + +You can implement translation in your Angular application in the following ways. + +- Using the [translate pipe](#translate-pipe) +- Using the [`t` attribute](#t-attribute) +- Using the [Translations methods](#translation-methods) + +## Translate pipe + +You can translate a string using the [`translate pipe`](/integrations/angular/api.mdx#translate-pipe). It accepts specific parameters and an optional default value. + +```html +

{{'hello_world' | translate}}

+``` + +To provide parameters for translation, pass them as the first parameter of the `translate` pipe. + +:::info +To enable parameter interpolation, you need to use a [message formatter](/formatting.mdx). +::: + +```html +

{{'hello' | translate:{ name: 'John Doe' }}}

+``` + +You can also provide a default value. + +```html +// with params +

{{'hello' | translate:{ name: 'John Doe' }:'Default!'}}

+ +// or without params +

{{'hello' | translate:'Default!'}}

+``` + +To disable wrapping, provide a `noWrap` option. + +```html +

{{ 'hello' | translate:{ noWrap: true } }}

+``` + +## `t` attribute + +You can also use a [`t` attribute](/integrations/angular/api.mdx#tcomponent) with an element for translation. Angular will render Tolgee component with `t` attribute selector. + +```html +

+``` + +To provide parameters for translation, pass them via the params attribute. + +:::info +To enable parameter interpolation, you need to use a [message formatter](/formatting.mdx). +::: + +```html +

+``` + +You can also provide a default value using the `default` attribute. + +```html +

+``` + +## Translation methods + +To translate texts in your component code, you can use [`translate`](/integrations/angular/api.mdx#method-translate) or [`instant`](/integrations/angular/api.mdx#method-instant) methods. + +These methods are part of `TranslateService` which can be injected using dependency injection as shown in the following example. + +```typescript +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@tolgee/ngx'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], +}) +export class AppComponent implements OnInit { + constructor(private translateService: TranslateService) {} + + helloWorld: string; + + async ngOnInit(): Promise { + this.translateService + .translate('hello_world') + .subscribe((r) => (this.helloWorld = r)); + } +} +``` + +`translate` method returns an [**Observable**](https://rxjs-dev.firebaseapp.com/guide/observable). As a result, the provided listener is called every time the translation changes due to language changes or other reasons. + +```typescript +this.translateService + .get('hello_world') + .subscribe((r) => (this.helloWorld = r)); +``` + +If you are unable to use this asynchronous approach for any reason, you can use the +`instant` method. + +:::warning +Don't overuse the `instant` method. Whenever possible, use the `translate` method. +When translations are not loaded, `instant` method will not provide a valid result. +::: + +```typescript +this.helloWorld = this.translateService.instant('hello_world'); +``` + +Both the `instant` and `translate` methods accept the same parameters as [`tolgee.t`](/api/core_package/tolgee.mdx#t). + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/api.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/api.mdx new file mode 100644 index 000000000..a1562ddb3 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/api.mdx @@ -0,0 +1,45 @@ +--- +id: api +title: Tolgee i18next API Reference +sidebar_label: API Reference +description: 'Full integration options and custom integration functions for granular control over i18next and Tolgee integration.' +--- + +## Full integration + +### function `withTolgee(i18next, tolgee)` + +Ensures complete integration between i18next and Tolgee. + +- `i18next` - the i18next instance +- `tolgee` - A [`TolgeeInstance`](/api/core_package/tolgee.mdx) + +## Custom integration + +For more granular control over the integration process, you can use the following functions individually instead of `withTolgee`. + +### function `tolgeeBackend(tolgee)` + +Creates a Tolgee [backend](https://www.i18next.com/misc/creating-own-plugins#backend) plugin for i18next. + +- `tolgee` - A Tolgee instance + +### function `tolgeeProcessor(tolgee)` + +Creates a Tolgee [postProcessor](https://www.i18next.com/misc/creating-own-plugins#post-processor) plugin for i18next. +Applies wrapping to each translation, making it detectable for in-context translation in development mode. + +- `tolgee` - A Tolgee instance + +### function `tolgeeApply(tolgee, i18next)` + +Registers necessary callbacks (`tolgee.onTranslationChange` and `i18next.on('languageChanged')`) on both Tolgee instance and i18next. + +- `tolgee` - A Tolgee instance +- `i18next` - An i18next instance + +### function `tolgeeOptions(options)` + +Extends i18next's [`InitOptions`](https://www.i18next.com/overview/configuration-options) with options necessary for Tolgee to function properly. This includes enabling the `tolgeeProcessor` globally and also reacting to store `added` event to refresh the UI when translations are changed through in-context translation. + +- `options` - i18next [`InitOptions`](https://www.i18next.com/overview/configuration-options) diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/installation.mdx new file mode 100644 index 000000000..61e9a5407 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/installation.mdx @@ -0,0 +1,88 @@ +--- +id: installation +title: Installation +sidebar_label: Installation +description: 'Learn how to integrate Tolgee with i18next to enhance your internationalization workflow while maintaining familiar i18next features.' +--- + +import PreparingForProduction from '../../shared/_PreparingForProduction.mdx'; +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; + +To use Tolgee with i18next, you need to follow these steps: + +1. [Install the Tolgee integration library](#installation) +2. [Set up the environment variables](#set-up-the-environment-variables) +3. [Initialize Tolgee](#initialize-tolgee) +4. [Language Detection and Switching](#language-detection-and-switching) +5. [Preparing for production](#preparing-for-production) + +## Installation + +To install the Tolgee i18next integration library, run the following command. + + + +## Set up the environment variables + +Once the library is installed, you need to initialize it. For initialization, you need the Tolgee API URL, and the Tolgee API Key. To generate the API Key, follow the step-by-step instructions mentioned on the [API keys and PAT tokens page](../../../../../platform/account_settings/api_keys_and_pat_tokens). + +:::danger +Make sure you don't leak your API key. If the API key is leaked, visitors can edit the translations on your site. +::: + +After generating the API key, add these credentials in an environment variable file (`.env` or `.env.local`). + +## Initialize Tolgee + +Next, initialize Tolgee and wrap your i18next instance using [`withTolgee`](./api#function-withtolgeei18next-tolgeeconfig). + +```ts +import i18n from 'i18next'; +import { withTolgee, Tolgee, I18nextPlugin, DevTools } from '@tolgee/i18next'; + +const tolgee = Tolgee() + .use(DevTools()) + .use(I18nextPlugin()) + .init({ + // for development + apiUrl: ..., + apiKey: ..., + + // for production + staticData: { + 'en:translation': ..., + 'cs:translation': ... + } + }); + +withTolgee(i18n, tolgee) + .use(...) + .init(...) + +``` + +The above code does the following: +- Initializes Tolgee with the provided configuration. +- Wraps the i18next instance with the `withTolgee` function. +- Initializes i18next with the provided configuration. + + +> You can configure more options and plugins during initialization. Learn about these other [options](../../api/core_package/options) and [Tolgee plugins](../../plugins) in their respective documentation. + +:::info Namespace Mapping +Tolgee maps `i18next` namespaces to its own namespaces. Since i18next doesn't support empty namespaces, ensure your translations are within a namespace in the Tolgee platform. By default, i18next uses the "translation" namespace. +::: + +## Language Detection and Switching + +Tolgee follows i18next's configuration for language detection. You can use i18next language detectors or set the language manually. + +To change the active language, use the `i18next.changeLanguage` function: + +```ts +i18next.changeLanguage(lng, callback); +``` + +## Preparing for production + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/react_integration.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/react_integration.mdx new file mode 100644 index 000000000..7697e4348 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/react_integration.mdx @@ -0,0 +1,50 @@ +--- +id: react +title: React integration using i18next +sidebar_label: React integration +slug: react-integration +description: 'Learn how to integrate Tolgee with React applications using i18next and react-i18next. This guide covers installation, setup, and best practices for internationalization in React projects.' +--- + +import {InstallationTabs} from "../../../../src/component/InstallationTabs"; +import ExampleBanner from '../../shared/ExampleBanner'; + +## Overview + +You can integrate Tolgee with React applications using `i18next` and `react-i18next`. It is recommend to use the ICU message formatter, since the Tolgee platform natively supported it. + +## Installation + +To get started, install the necessary packages: + + + +## Setup + +After installation, set up your i18next instance with Tolgee and React integration: + +```ts +import { withTolgee, Tolgee, I18nextPlugin, DevTools } from '@tolgee/i18next'; +import i18n from 'i18next'; +import ICU from 'i18next-icu'; +import { initReactI18next } from 'react-i18next'; + +const tolgee = Tolgee().use(DevTools()).use(I18nextPlugin()).init({ + apiUrl: process.env.VITE_APP_TOLGEE_API_URL, + apiKey: process.env.VITE_APP_TOLGEE_API_KEY, +}); + +withTolgee(i18n, tolgee) + .use(ICU) + .use(initReactI18next) + .init({ + lng: 'en', // or use i18next language detector + supportedLngs: ['cs', 'en', 'fr', 'de'], + }); +``` + +You can now use the features of [react-i18next](https://react.i18next.com/). + +Feel free to explore the example application for more advanced usage and integration patterns. + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/vue_integration.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/vue_integration.mdx new file mode 100644 index 000000000..05f97d32d --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/i18next/vue_integration.mdx @@ -0,0 +1,63 @@ +--- +id: vue +title: Vue integration using i18next +sidebar_label: Vue integration +slug: vue-integration +description: 'Learn how to integrate Tolgee with Vue 2 applications using i18next and @panter/vue-i18next. This guide covers installation, setup, and usage for effective internationalization in Vue 2 projects.' +--- + +import {InstallationTabs} from "../../../../src/component/InstallationTabs"; +import ExampleBanner from '../../shared/ExampleBanner'; + +:::caution +This integration is specifically for Vue 2. +::: + +## Overview + +You can integrate Tolgee with Vue2 applications using `i18next` and `@panter/vue-i18next`. It is recommend to use the ICU message formatter, since the Tolgee platform natively supported it. + +## Installation + +To get started, install the necessary packages: + + + + +## Setup + +After installation, set up your i18next instance with Tolgee and Vue integration: + +```ts +import { withTolgee, I18nextPlugin, DevTools } from '@tolgee/i18next'; +import i18next from 'i18next'; +import ICU from 'i18next-icu'; +import VueI18Next from '@panter/vue-i18next'; + +Vue.use(VueI18Next); + +const tolgee = Tolgee().use(DevTools()).use(I18nextPlugin()).init({ + apiUrl: process.env.VUE_APP_TOLGEE_API_URL, + apiKey: process.env.VUE_APP_TOLGEE_API_KEY, +}); + +withTolgee(i18next, tolgee) + .use(ICU) + .init({ + lng: 'en', // or use i18next language detector + supportedLngs: ['cs', 'en', 'fr', 'de'], + }); + +const i18n = new VueI18Next(i18next); + +new Vue({ + i18n, + ... +}).$mount('#app'); +``` + +You can now use the features of [@panter/vue-i18next](https://panter.github.io/vue-i18next/). + +Feel free to explore the example application for more advanced usage and integration patterns. + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/api.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/api.mdx new file mode 100644 index 000000000..9e861a0cd --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/api.mdx @@ -0,0 +1,176 @@ +--- +id: api +title: API (React) +sidebar_label: API +description: 'API documentation for React integration: TolgeeProvider, T-component and different hooks ( useCurrentLanguage, useSetLanguage, useTranslate).' +--- + +## TolgeeProvider + +Provides Tolgee context and takes care of running/stopping tolgee. Accepts [`Tolgee`](/api/core_package/tolgee.mdx) instance. + +```jsx +import { TolgeeProvider } from '@tolgee/react'; + + + +; +``` + +> You can use Tolgee without TolgeeProvider, but you'll have to run `tolgee.run` yourself and handle initial loading. + +### Prop `fallback?` + +`React.Node` - it is rendered when Tolgee is loading translations instead of provided content. + +### Prop `options?` + +```ts +{ + useSuspense?: boolean +} +``` + +- useSuspense: boolean - `TolgeeProvider` and `useTranslate` will use suspense pattern (default: true) + +## T component + +```jsx +import { T } from '@tolgee/react'; + + + default value ... +; +``` + +### Prop `keyName?` + +`String` - translation key. + +### Prop `defaultValue?` + +`String` - default value if translation is not present. + +### Prop `params?` + +`Record ReactNode>` - variable parameters, which can be used in translation value +(read more about [ICU message format](/platform/translation_process/icu_message_format)). + +### Prop `ns?` + +`string` - translation namespace + +### Prop `noWrap?` + +`Boolean` (default: `false`) + +- `false` - in development mode translation will be [wrapped](/wrapping.mdx) +- `true` - use when wrapping in dev mode causes problems, in this case in-context translation won't work + +### Prop `language?` + +`String` - override current Tolgee language. This way you can switch to different language for separate translation. Load the language manually with [`tolgee.loadRecord`](/api/core_package/tolgee.mdx#loadrecord). + +### Children `defaultValue?` | `keyName?` + +`String` - If `keyName` property is not defined, child is taken as `keyName`. If it is present child can be used as `defaultValue`. + +## Hook `useTranslate` + +Use this hook to get `t` function for translating. It also serves for loading namespaces. + +```ts +function useTranslate(ns?: string | string[]): { + t: TFnType; + isLoading: boolean; +}; +``` + +This hook triggers suspense if specified namespace(s) are not yet available. Use it for loading namespaces for specific components/pages (returned `t` function uses first namespace from the list automatically). + +### Parameter `ns` + +- `string` | `string[]` - namespace(s) which will be loaded + +### Property `isLoading` + +- `boolean` - is true if any of listed namespaces is not loaded yet + +Use this property if you manually disable `useSuspense`, to make sure you won't display translation keys. + +### Function `t` + +Returns requested translation and also subscribes component to translation/language changes, so component will be re-rendered every time translation changes. If you use namespaces, `t` function will automatically use first of the namespaces given to `useTranslate` function. You can override this by `ns` option. + +```ts +t('key', 'Default value', ); +``` + +> `t` function has the same API as [`tolgee.t`](/api/core_package/tolgee.mdx#t). + +## Hook `useTolgee` + +```ts +function useTolgee(events?: TolgeeEvent[]): TolgeeInstance; +``` + +Returns tolgee instance. Allows subscription to different [`events`](/api/core_package/events.mdx), which will trigger re-render. + +```tsx +const RenderingLanguage = () => { + // will update the component on "language" event + const tolgee = useTolgee(['language']); + + return
Language: {tolgee.getLanguage()}
+}; + +const JustGettingTolgeeInstance = () => { + // won't trigger rerender + const tolgee = useTolgee(); + + ... +}; + +const WorksAlsoWithOtherEvents = () => { + const tolgee = useTolgee(['loading']); + + return
Tolgee is loading: {tolgee.isLoading()}
+}; +``` + +## Hook `useTolgeeSSR` + +```ts +function useTolgeeSSR( + tolgee: TolgeeInstance, + language?: string, + staticData?: TolgeeStaticData +): TolgeeInstance; +``` + +Safely syncs tolgee language and adds static data to tolgee cache for the initial SSR render. + +It returns a "shallow copy" of tolgee instance which will ensure the first render is the same on server and client. So make sure you pass this copy to `TolgeeProvider`. + +:::info +`staticData` need to be in tolgee format which is accepted by [`tolgee.addStaticData`](/api/core_package/tolgee.mdx#addstaticdata) +::: +Usage: + +```tsx + +const tolgee = ... + +function App() { + const locale = // server provided locale + const staticData = // server provided static data + + const ssrTolgee = useTolgeeSSR(tolgee, locale, staticData); + + return ( + + ... + + ) +} +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/installation.mdx new file mode 100644 index 000000000..c6c6d8534 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/installation.mdx @@ -0,0 +1,94 @@ +--- +id: installation +title: Installation +sidebar_label: Installation +description: "Tolgee enables you to implement localization in your React application with ease. You can integrate Tolgee in your React application using Tolgee's SDK for React. To use this SDK, start by installing Tolgee for React." +--- + +To use Tolgee in your React application, you need to follow these steps: + +1. [Install the Tolgee SDK](#install-the-tolgee-sdk) +2. [Configure the Tolgee API](#configure-the-tolgee-api) +3. [Add the Tolgee Provider](#add-the-tolgee-provider) + +## Install the Tolgee SDK + +To install Tolgee's React integration SDK, execute the following command: + +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; + + + +## Configure the Tolgee API + +Once the SDK is installed, you need to initialize it. For the initialization, you need the Tolgee API URL, and the Tolgee API Key. To generate the API Key, follow the step-by-step instructions mentioned on the [API keys and PAT tokens page](../../../../../platform/account_settings/api_keys_and_pat_tokens). + +:::danger +Make sure you don't leak your API key. If the API key is leaked, visitors can edit the translations on your site. +::: + +After generating the API key, add these credentials in your `.env` file. Your `.env` should look like this: + +```env +VITE_APP_TOLGEE_API_URL=https://app.tolgee.io +VITE_APP_TOLGEE_API_KEY=tgpak_gfpwiojtnrztqmtbna3dczjxny2ha3dmnu4tk4tnnjvgc +``` + +:::note +If you are using a React-based framework like Next.js, make sure you paste these credentials in the correct file. +::: + +## Add the Tolgee Provider + +Next, use the above credentials to initialize Tolgee in your React application. You can use Tolgee across your React application by using the [`TolgeeProvider`](./api#tolgeeprovider). + +Wrap your `App` component within the TolgeeProvider, as show in the code below. + +```tsx +import { Tolgee, DevTools, TolgeeProvider, FormatSimple } from "@tolgee/react"; + +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + language: 'en', + + // for development + apiUrl: process.env.VITE_APP_TOLGEE_API_URL, + apiKey: process.env.VITE_APP_TOLGEE_API_KEY, + + // for production + staticData: { + ... + } + }); + +... + + + + +``` + +The above code does the following: + +- Imports the required classes, plugins and the TolgeeProvider from the SDK. +- Creates a new instance of Tolgee, configures it to use the [DevTools](../../api/web_package/plugins#devtools) and [FormatSimple](../../api/core_package/format-simple) plugins, and initializes it using the credentials. +- Passes the instance to the TolgeeProvider and sets a fallback. +- Uses TolgeeProvider to wrap the component. + +> You can configure more options and plugins during initialization. Learn about these other [options](../../api/core_package/options) and [Tolgee plugins](../../plugins) in their respective documentation. + +## Prepare for production + +import PreparingForProduction from '../../shared/_PreparingForProduction.mdx'; + + + +import ExampleBanner from '../../shared/ExampleBanner'; + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router.mdx new file mode 100644 index 000000000..079083391 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router.mdx @@ -0,0 +1,305 @@ +--- +id: app-router +sidebar_label: App router +title: Next.js with App Router +description: "Get started with Tolgee's integration for Next.js. Learn how to add localization to your Next.js applications that uses the App Router." +--- + +import AppRouterTranslating from './shared/_AppRouterTranslating.mdx' +import ExampleBanner from '../../../shared/ExampleBanner'; +import LimitationsOfServerComponents from './shared/_LimitationsOfServerComponents.mdx' + + + + +Follow the below instructions to learn to implement localization to your Next.js App Router app with Tolgee. + +In this example we'll store language in cookies, which is a simple to implement, but is not ideal for SEO. If you want a more robust solution with language in url, see [App router with next-intl](./app_router_next_intl.mdx) + +## Prerequisites + +1. An existing Next.js project. +2. An existing [project](../../../../../platform/getting_started/creating_project) on Tolgee platform with at least 2 languages. This guide uses English (en) and Czech (cs). +3. Add localization keys and translations for both the languages. This guide uses the key name `hello_world`. +4. [API key](../../../../../platform/account_settings/api_keys_and_pat_tokens) of your Tolgee project. + +## Install the required packages + +To implement localization to your app, you need to install the `@tolgee/react` package: + +```sh +npm install @tolgee/react +``` + +:::info +Use `@tolgee/react` version `5.30.0` and higher +::: + +## Folder structure + +The folder structure of your project should resemble the following: + +``` +├── .env.development.local # ignored by git +├── next.config.js +├── messages +│ ├── en.json +│ └── cs.json +└── src + ├── tolgee + │ ├── shared.ts + │ ├── language.ts + │ ├── client.tsx + │ └── server.tsx + └── app + ├── layout.tsx + └── page.tsx +``` + +## Set up your environment + +Create the `.env.development.local` file if it does not exist. You will store the Tolgee credentials securely in this file. + +Paste the following in the newly created file. Replace `` with your Tolgee API key. + +```sh title=".env.development.local" +NEXT_PUBLIC_TOLGEE_API_KEY= +NEXT_PUBLIC_TOLGEE_API_URL=https://app.tolgee.io +``` + +## Save exported data + +Create an `messages` folder in the root of your project directory, if it does not already exists. Move the exported localization json files to the `messages` folder. + + +## Set up Tolgee + +You need to set up Tolgee for both the client and the server. You can create shared configuration that can be used for both client and server. + +### Shared configuration + +In your `tolgee/shared.ts` file, add the following code. + +```ts title="src/tolgee/shared.ts" +import { DevTools, Tolgee, FormatSimple } from '@tolgee/web'; + +const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY; +const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL; + +export const ALL_LANGUAGES = ['en', 'cs']; + +export const DEFAULT_LANGUAGE = 'en'; + +export async function getStaticData( + languages: string[], + namespaces: string[] = [''] +) { + const result: Record = {}; + for (const lang of languages) { + for (const namespace of namespaces) { + if (namespace) { + result[`${lang}:${namespace}`] = ( + await import(`../../messages/${namespace}/${lang}.json`) + ).default; + } else { + result[lang] = (await import(`../../messages/${lang}.json`)).default; + } + } + } + return result; +} + +export function TolgeeBase() { + return Tolgee() + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .use(DevTools()) + .updateDefaults({ + apiKey, + apiUrl, + }); +} +``` + +### Client configutation + +The client configuration is very similar to the [Pages Router set up](./pages-router). It serves the purpose of translating client components and also enables the in-context functionality for server-rendered components. + +```tsx title="src/tolgee/client.tsx" +'use client'; + +import { TolgeeBase } from './shared'; +import { TolgeeProvider, TolgeeStaticData } from '@tolgee/react'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; + +type Props = { + language: string; + staticData: TolgeeStaticData; + children: React.ReactNode; +}; + +const tolgee = TolgeeBase().init(); + +export const TolgeeNextProvider = ({ + language, + staticData, + children, +}: Props) => { + const router = useRouter(); + + useEffect(() => { + const { unsubscribe } = tolgee.on('permanentChange', () => { + router.refresh(); + }); + return () => unsubscribe(); + }, [tolgee, router]); + + return ( + + {children} + + ); +}; +``` + +### Storing language in cookies + +To store user language create file `language.ts`, important part here is the `"use server"` directive, so we can call `setLanguage` method from frontend as server action. + +```tsx title="src/tolgee/language.ts" +'use server'; + +import { detectLanguageFromHeaders } from '@tolgee/react/server'; +import { cookies, headers } from 'next/headers'; +import { ALL_LANGUAGES, DEFAULT_LANGUAGE } from './shared'; + +const LANGUAGE_COOKIE = 'NEXT_LOCALE'; + +export async function setLanguage(locale: string) { + const cookieStore = cookies(); + cookieStore.set(LANGUAGE_COOKIE, locale, { + maxAge: 1000 * 60 * 60 * 24 * 365, // one year in milisecods + }); +} + +export async function getLanguage() { + const cookieStore = cookies(); + const locale = cookieStore.get(LANGUAGE_COOKIE)?.value; + if (locale && ALL_LANGUAGES.includes(locale)) { + return locale; + } + + // try to detect language from headers or use default + const detected = detectLanguageFromHeaders(headers(), ALL_LANGUAGES); + return detected || DEFAULT_LANGUAGE; +} +``` + + +### Server configuration + +Your app will utilize the React server cache for sharing Tolgee instance across components in a single render. This allows the app to use the Tolgee instance anywhere in the server components. + +One important difference from the client setup is the utilization of [`fullKeyEncode`](../../../api/core_package/observer-options#fullkeyencode). `fullKeyEncode` ensures that translations rendered on the server are correctly picked up and interactive for in-context mode. + +```tsx title="src/tolgee/server.tsx" +import { TolgeeBase, ALL_LANGUAGES, getStaticData } from './shared'; +import { createServerInstance } from '@tolgee/react/server'; +import { getLanguage } from './language'; + +export const { getTolgee, getTranslate, T } = createServerInstance({ + getLocale: getLanguage, + createTolgee: async (locale) => + TolgeeBase().init({ + // including all locales + // on server we are not concerned about bundle size + staticData: await getStaticData(ALL_LANGUAGES), + observerOptions: { + fullKeyEncode: true, + }, + language: locale, + fetch: async (input, init) => + fetch(input, { ...init, next: { revalidate: 0 } }), + }), +}); +``` + +## Use the `TolgeeNextProvider` + +The next step is to wrap the children with the `TolgeeNextProvider`. Update the `layout.tsx` file with the following code: + +```tsx title="src/app/layout.tsx" +import { ReactNode } from 'react'; +import { TolgeeNextProvider } from '@/tolgee/client'; +import { getStaticData } from '@/tolgee/shared'; +import { getLanguage } from '@/tolgee/language'; +import './style.css'; + +type Props = { + children: ReactNode; + params: { locale: string }; +}; + +export default async function LocaleLayout({ children }: Props) { + const locale = await getLanguage(); + // it's important you provide all data which are needed for initial render + // so current language and also fallback languages + necessary namespaces + const staticData = await getStaticData([locale]); + + return ( + + + + {children} + + + + ); +} +``` + +The above code loads relevant locale available on the server and pass it to the client component through props. + +:::info +Make sure you are not using `export const dynamic = 'force-static'` on your pages, as that makes it impossible to use cookies ([more info](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config)). +::: + + + +## Switching Languages + +For switching languages use `setLanguage` server action from `language.ts`, which will just update our cookie. + +```tsx +'use client'; + +import React from 'react'; +import { useTolgee } from '@tolgee/react'; +import { setLanguage } from '@/tolgee/language'; + +export const LangSelector: React.FC = () => { + const tolgee = useTolgee(['language']); + const language = tolgee.getLanguage(); + + function onSelectChange(e) { + setLanguage(e.target.value); + } + + return ( + + ); +}; +``` + + + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router_next_intl.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router_next_intl.mdx new file mode 100644 index 000000000..d6e07bfc4 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/app_router_next_intl.mdx @@ -0,0 +1,363 @@ +--- +id: app-router-next-intl +sidebar_label: App router with next-intl +title: Next.js with App Router & router-based localizaton +description: "Get started with Tolgee's integration for Next.js. Learn how to add localization to your Next.js applications that uses next-intl package with router-based localization." +--- + +import AppRouterTranslating from './shared/_AppRouterTranslating.mdx' +import ExampleBanner from '../../../shared/ExampleBanner'; +import LimitationsOfServerComponents from './shared/_LimitationsOfServerComponents.mdx' + +This guide shows how to include language information into the page URL, so your application is SEO friendly. + + + +## Installation + +The Next.js App Router does not provide native support for `i18n` routing as the Page Router does. However, you can use the [`next-intl`](https://next-intl-docs.vercel.app/) library, for routing and locale detection. + +We are not using an entire functionality of the library, so the setup is missing some pieces from official documentation of [next-intl](https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing). + +Follow the below instructions to learn to implement localization to your Next.js App Router app with Tolgee. + +## Prerequisites + +1. An existing Next.js project. +2. An existing [project](../../../../../platform/getting_started/creating_project) on Tolgee platform with at least 2 languages. This guide uses English (en) and Czech (cs). +3. Add localization keys and translations for both the languages. This guide uses the key name `hello_world`. +4. [API key](../../../../../platform/account_settings/api_keys_and_pat_tokens) of your Tolgee project. + +## Install the required packages + +To implement localization to your app, you need to install the `next-intl` and `@tolgee/react` package. Execute the following command to install the package. + +```sh +npm install next-intl @tolgee/react +``` + +:::info +Use `@tolgee/react` version `5.30.0` and higher +::: + +## Folder structure + +The folder structure of your project should resemble the following: + +``` +├── .env.development.local # ignored by git +├── next.config.js +├── messages +│ ├── en.json +│ └── cs.json +└── src + ├── middleware.ts + ├── navigation.ts + ├── i18n + │ └── request.ts + ├── tolgee + │ ├── shared.ts + │ ├── client.tsx + │ └── server.tsx + └── app + └── [locale] + ├── layout.tsx + └── page.tsx +``` + +## Set up your environment + +Create the `.env.development.local` file if it does not exist. You will store the Tolgee credentials securely in this file. + +Paste the following in the newly created file. Replace `` with your Tolgee API key. + +```sh title=".env.development.local" +NEXT_PUBLIC_TOLGEE_API_KEY= +NEXT_PUBLIC_TOLGEE_API_URL=https://app.tolgee.io +``` + +## Save exported data + +Create an `messages` folder in the root of your project directory, if it does not already exists. Move the exported localization json files to the `messages` folder. + + +## Set up Tolgee + +You need to set up Tolgee for both the client and the server. You can create shared configuration that can be used for both client and server. + +### Shared configuration + +In your `tolgee/shared.ts` file, add the following code. + +```ts title="src/tolgee/shared.ts" +import { DevTools, Tolgee, FormatSimple } from '@tolgee/web'; + +const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY; +const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL; + +export const ALL_LANGUAGES = ['en', 'cs', 'de', 'fr']; + +export const DEFAULT_LANGUAGE = 'en'; + +export async function getStaticData( + languages: string[], + namespaces: string[] = [''] +) { + const result: Record = {}; + for (const lang of languages) { + for (const namespace of namespaces) { + if (namespace) { + result[`${lang}:${namespace}`] = ( + await import(`../../messages/${namespace}/${lang}.json`) + ).default; + } else { + result[lang] = (await import(`../../messages/${lang}.json`)).default; + } + } + } + return result; +} + +export function TolgeeBase() { + return Tolgee() + .use(FormatSimple()) + .use(DevTools()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .updateDefaults({ + apiKey, + apiUrl, + }); +} + +``` + +### Client configutation + +The client configuration is very similar to the [Pages Router set up](./pages-router). It serves the purpose of translating client components and also enables the in-context functionality for server-rendered components. + +```tsx title="src/tolgee/client.tsx" +'use client'; + +import { useEffect } from 'react'; +import { TolgeeProvider, TolgeeStaticData } from '@tolgee/react'; +import { useRouter } from 'next/navigation'; +import { TolgeeBase } from './shared'; + +type Props = { + staticData: TolgeeStaticData; + language: string; + children: React.ReactNode; +}; + +const tolgee = TolgeeBase().init(); + +export const TolgeeNextProvider = ({ + language, + staticData, + children, +}: Props) => { + const router = useRouter(); + + useEffect(() => { + const { unsubscribe } = tolgee.on('permanentChange', () => { + router.refresh(); + }); + return () => unsubscribe(); + }, [tolgee, router]); + + return ( + + {children} + + ); +}; + +``` + +### Server configuration + +Your app will utilize the React server cache for sharing Tolgee instance across components in a single render. This allows the app to use the Tolgee instance anywhere in the server components. + +One important difference from the client setup is the utilization of [`fullKeyEncode`](../../../api/core_package/observer-options#fullkeyencode). `fullKeyEncode` ensures that translations rendered on the server are correctly picked up and interactive for in-context mode. + +```tsx title="src/tolgee/server.tsx" +import { getLocale } from 'next-intl/server'; + +import { TolgeeBase, ALL_LANGUAGES, getStaticData } from './shared'; +import { createServerInstance } from '@tolgee/react/server'; + +export const { getTolgee, getTranslate, T } = createServerInstance({ + getLocale: getLocale, + createTolgee: async (language) => + TolgeeBase().init({ + // including all languages + // on server we are not concerned about bundle size + staticData: await getStaticData(ALL_LANGUAGES), + observerOptions: { + fullKeyEncode: true, + }, + language, + // using custom fetch to avoid aggressive caching + fetch: async (input, init) => + fetch(input, { ...init, next: { revalidate: 0 } }), + }), +}); +``` + +## Use the `TolgeeNextProvider` + +The next step is to wrap the children with the `TolgeeNextProvider`. Update the `layout.tsx` file with the following code: + +```tsx title="src/app/[locale]/layout.tsx" +import { notFound } from 'next/navigation'; +import { ReactNode } from 'react'; +import { TolgeeNextProvider } from '@/tolgee/client'; +import { ALL_LANGUAGES, getStaticData } from '@/tolgee/shared'; + +type Props = { + children: ReactNode; + params: { locale: string }; +}; + +export default async function LocaleLayout({ + children, + params: { locale }, +}: Props) { + if (!ALL_LANGUAGES.includes(locale)) { + notFound(); + } + + // it's important you provide all data which are needed for initial render + // so current language and also fallback languages + necessary namespaces + const staticData = await getStaticData([locale, 'en']); + + return ( + + + + {children} + + + + ); +} +``` + +The above code loads relevant locale available on the server and pass it to the client component through props. + +> This example provides translation globally. If you want to provide each page with a different namespace, you can move the provider to the relevant page files. + +## Set up `next-intl` + +To use `next-intl` for routing and language detection, you need to set `middleware` and `navigation`. Create a `src/middleware.ts` file if it does not exist. Add the following code in this file. + +```tsx title="src/middleware.ts" +import createMiddleware from 'next-intl/middleware'; +import { ALL_LANGUAGES, DEFAULT_LANGUAGE } from '@/tolgee/shared'; + +// read more about next-intl middleware configuration +// https://next-intl-docs.vercel.app/docs/routing/middleware#locale-prefix +export default createMiddleware({ + locales: ALL_LANGUAGES, + defaultLocale: DEFAULT_LANGUAGE, + localePrefix: 'as-needed', +}); + +export const config = { + // Skip all paths that should not be internationalized + matcher: ['/((?!api|_next|.*\\..*).*)'], +}; +``` + +Next, create a `src/navigation.ts` file. This provides easy access to the navigation APIs in your components. + +```ts title="src/navigation.ts" +import { createSharedPathnamesNavigation } from 'next-intl/navigation'; +import { ALL_LANGUAGES } from './tolgee/shared'; + +// read more about next-intl library +// https://next-intl-docs.vercel.app +export const { Link, redirect, usePathname, useRouter } = + createSharedPathnamesNavigation({ locales: ALL_LANGUAGES }); +``` + +> To gain a comprehensive understanding of how `next-intl` operates, read [their documentation](https://next-intl-docs.vercel.app/docs/getting-started/app-router-server-components). This example utilizes the necessary setup for proper routing. Not all the listed configurations are required. + +### Update `next.config.js` + +You also need to update the `next.config.js` file as follows. + +```js title="next.config.js" +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin(); + +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default withNextIntl(nextConfig); +``` + +### Add `i18n/request.ts` file + +The `i18n/request.ts` is required by `next-intl` package, we don't actually need it, so we are only doing necessary actions to stop `next-intl` from complaining. + +```ts title="src/i18n.ts" +import { getRequestConfig } from 'next-intl/server'; +import { getStaticData } from '../tolgee/shared'; + +export default getRequestConfig(async ({ locale }) => { + return { + // do this to make next-intl not emitting any warnings + messages: { locale }, + }; +}); +``` + + + +## Switching Languages + +For switching languages you should use the `next-intl` router helpers. + +```tsx +'use client'; + +import React, { ChangeEvent, useTransition } from 'react'; +import { usePathname, useRouter } from '@/navigation'; +import { useTolgee } from '@tolgee/react'; + +export const LangSelector: React.FC = () => { + const tolgee = useTolgee(['language']); + const locale = tolgee.getLanguage(); + const router = useRouter(); + const pathname = usePathname(); + const [isPending, startTransition] = useTransition(); + + function onSelectChange(event: ChangeEvent) { + const newLocale = event.target.value; + startTransition(() => { + router.replace(pathname, { locale: newLocale }); + }); + } + return ( + + ); +}; +``` + +:::info +Make sure you use navigation-related components from `@/navigation` instead natively from Next.js. This ensures that the correct locale is passed to the route. +::: + + + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/introduction.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/introduction.mdx new file mode 100644 index 000000000..99ba09bfe --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/introduction.mdx @@ -0,0 +1,19 @@ +--- +id: introduction +sidebar_label: Overview +title: Tolgee with Next.js +description: "Get started with Tolgee's integration for Next.js. Learn how to add localization to your Next.js applications." +--- + +You can use the [Tolgee React SDK](../overview) to implement localization to your Next.js application. The SDK provides additional helper functions on top of React integration for easy setup. These helper functions allow you to use the SDK suiting every use case. + +## Integration guides + + - [Next.js with pages router](./pages-router) + - [Next.js with app router](./app-router) + +Advanced + - [Next.js with next-intl and app router](./app-router-next-intl) - route based localization + + +> To learn to install the SDK follow the [React installation](../installation) documentation. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/pages_router.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/pages_router.mdx new file mode 100644 index 000000000..55c97fe40 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/pages_router.mdx @@ -0,0 +1,193 @@ +--- +id: pages-router +sidebar_label: Pages router +title: Next.js with Pages Router +description: "Get started with Tolgee's integration for Next.js. Learn how to add localization to your Next.js applications that uses the Pages Router." +--- + +import ExampleBanner from '../../../shared/ExampleBanner'; + + + +## Prerequisites + +1. An existing Next.js project. Install the [`@tolgee/react`](https://www.npmjs.com/package/@tolgee/react) package. +2. An existing [project](../../../../../platform/getting_started/creating_project) on Tolgee platform with at least 2 languages. This guide uses English (en) and Czech (cs). +3. Add localization keys and translations for both the languages. This guide uses the key name `hello_world`. +4. [API key](../../../../../platform/account_settings/api_keys_and_pat_tokens) of your Tolgee project. +5. Exported localization data in JSON format. + +## Install the required packages + +To implement localization to your app, you need to install the `@tolgee/react` package: + +```sh +npm install @tolgee/react +``` + +:::info +Use `@tolgee/react` version `5.30.0` and higher +::: + +## Folder structure + +The folder structure of your project should resemble the following: + +``` +├── .env.development.local # ignored by git +├── next.config.js +├── messages +│ ├── en.json +│ └── cs.json +└── src + ├── tolgee.ts + └── pages + └── _app.tsx +``` + +## Prepare your next-config + +Add the [`i18n` config](https://nextjs.org/docs/pages/building-your-application/routing/internationalization) into your `next-config.js` file as follow. + +```js title="next.config.js" +module.exports = { + reactStrictMode: true, + i18n: { + locales: ['en', 'cs'], + defaultLocale: 'en', + }, +}; +``` + +## Set up your environment + +Create the `.env.development.local` file if it does not exist. You will store the Tolgee credentials securely in this file. + +Paste the following in the newly created file. Replace `` with your Tolgee API key. + +```sh title=".env.development.local" +NEXT_PUBLIC_TOLGEE_API_KEY= +NEXT_PUBLIC_TOLGEE_API_URL=https://app.tolgee.io +``` + +## Save exported data + +Create an `messages` folder in the root of your project directory, if it does not already exists. Move the exported localization json files to the `messages` folder. + +## Create Tolgee setup file + +This file contains tolgee setup and helper function for loading static files. + +```ts title="src/tolgee.ts" +import { FormatIcu } from '@tolgee/format-icu'; +import { Tolgee, DevTools } from '@tolgee/react'; + +export async function getStaticData( + languages: string[], + namespaces: string[] = [''] +) { + const result: Record = {}; + for (const lang of languages) { + for (const namespace of namespaces) { + if (namespace) { + result[`${lang}:${namespace}`] = ( + await import(`../messages/${namespace}/${lang}.json`) + ).default; + } else { + result[lang] = (await import(`../messages/${lang}.json`)).default; + } + } + } + return result; +} + +export const tolgee = Tolgee() + .use(FormatIcu()) + .use(DevTools()) + .init({ + availableLanguages: ['en', 'cs'], + defaultLanguage: 'en', + apiKey: process.env.NEXT_PUBLIC_TOLGEE_API_KEY, + apiUrl: process.env.NEXT_PUBLIC_TOLGEE_API_URL, + }); + +``` + + +## Add the TolgeeProvider + +The next step is to wrap the application's main component with the [`TolgeeProvider` component](../api#tolgeeprovider). + +Import the localization data and provide them to the `TolgeeProvider` using the `staticData` prop. + +```tsx title="src/pages/_app.tsx" +import type { AppContext, AppInitialProps, AppProps } from 'next/app'; +import { getStaticData, tolgee } from '../tolgee'; +import App from 'next/app'; +import { TolgeeProvider, TolgeeStaticData } from '@tolgee/react'; +import { useRouter } from 'next/router'; + +type AppOwnProps = { staticData: TolgeeStaticData }; + +export default function MyApp({ + Component, + pageProps, + staticData, +}: AppProps & AppOwnProps) { + const router = useRouter(); + return ( + + + + ); +} + + +MyApp.getInitialProps = async ( + context: AppContext +): Promise => { + const ctx = await App.getInitialProps(context); + return { ...ctx, staticData: await getStaticData([context.ctx.locale!]) }; +}; +``` + +Similarly, you can use [`getServerSideProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props) or [`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props) on each page. + +## Change language with the Next.js router + +The application needs locale information both on server and client. The application can get this infromation native way as follows. + +```tsx +import { useRouter } from 'next/router'; + +export const LangSelector: React.FC = () => { + const router = useRouter(); + const setLanguage = (lang: string) => { + router.replace(router.pathname, undefined, { locale: lang }); + }; + + return ( + + ); +}; +``` + +## Use T component for translation + +The next step is to render the translated text for the selected locale. Use the [`T` component](../api#t-component) to translate the text in your app. + +```tsx +

+ +

+``` + +That's it! You have successfully implemented translation to your Next.js applicaiton. You can also use translation methods described in the [React Translating documentation](../translating.mdx). + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_AppRouterTranslating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_AppRouterTranslating.mdx new file mode 100644 index 000000000..e3064d37e --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_AppRouterTranslating.mdx @@ -0,0 +1,45 @@ + +## Localizing Server components + +Now that everything is setup, you can use the `getTranslate` function to render the translated text. Since this is a server component, you use `getTranslate` instead of the `useTranslate` hook. + +```tsx title="src/page.tsx" +import { getTranslate } from '@/tolgee/server'; + +export default async function IndexPage() { + // because this is server component, use `getTranslate` + // not useTranslate from '@tolgee/react' + const t = await getTranslate(); + return ( +
+

{t('hello_world')}

+
+ ); +} +``` + +If everything is set up correctly, you could use in-context translation. Press and holde the `alt` (or `option`) key, and click `hello_world`. This will open the in-context pop-up window. + +## Localizing Client components + +For client components, you can use the `useTranslate` hook or the `t-component`. + +```tsx +'use client'; + +import { useTranslate } from '@tolgee/react'; + +export const ExampleClientComponent = () => { + const { t } = useTranslate(); + + return ( +
+ {t('example-key-in-client-component')} +
+ ); +}; +``` + +:::info +Make sure to use `@/tolgee/server` in server components and `@tolgee/react` in client components. +::: diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_LimitationsOfServerComponents.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_LimitationsOfServerComponents.mdx new file mode 100644 index 000000000..0a5883de2 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/next/shared/_LimitationsOfServerComponents.mdx @@ -0,0 +1,5 @@ +## Limitations of Server Components + +Although in-context translation works with server components, there are some limitations compared to client components. The Tolgee cache on the server is separate. This prevents Tolgee from automatically changing the translation when creating a screenshot (unlike with client components, which swap the content if you've modified it in a dialog). + +Furthermore, if you're using the [Tolgee browser plugin](https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj), it won't affect the server's transition to dev mode. As a result, only the client switches, leaving server components non-editable in this mode. \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/overview.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/overview.mdx new file mode 100644 index 000000000..9bd663b5c --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/overview.mdx @@ -0,0 +1,13 @@ +--- +id: overview +title: Tolgee with React +sidebar_label: Overview +description: "Get started with Tolgee's integration for React. Learn how to add localization to your React applications, including support for Next.js and React Native." +--- + +Tolgee provides an SDK for React, based on the [`@tolgee/web`](../../api/web_package/about) package. Using the SDK [@tolgee/react](https://www.npmjs.com/package/@tolgee/react), you can connect your React application to the Tolgee platform. This SDK enables you to implement [in-context translations](../../in-context), [language detection](../../language#language-detection), interpolation, and other advanced features. + +- Follow the [installation guide](./installation) to add the Tolgee React SDK to your existing React application. +- You can also check the [example application](https://github.com/tolgee/react-example) to see how to integrate Tolgee with React. +- If you are building your application with [Next.js](https://nextjs.org/), follow our [Next.js documentation](./next/introduction) for guidance. +- For React Native applications, view our [React Native documentation](./react_native) to learn how to use Tolgee. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/react_native.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/react_native.mdx new file mode 100644 index 000000000..d0bef4f86 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/react_native.mdx @@ -0,0 +1,66 @@ +--- +id: react_native +title: React Native +description: "Learn how to use Tolgee with React Native. To get full image of working React integration check our React Native example application." +--- + +import ExampleBanner from '../../shared/ExampleBanner'; + + + +For React Native we recommend following setup (with [Expo](https://expo.dev/)): + +```tsx +import { + Tolgee, + DevTools, + TolgeeProvider, + FormatSimple +} from "@tolgee/react"; + +import en from "./i18n/en.json"; +import cs from "./i18n/cs.json"; +import { TOLGEE_API_KEY, TOLGEE_API_URL } from "@env"; + + +const tolgee = Tolgee() + // DevTools will work only for web view + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + language: 'en', + + // for development + apiUrl: TOLGEE_API_URL, + apiKey: TOLGEE_API_KEY, + + // include translations statically + staticData: { en, cs }, + }); + +... + + + + +``` + +You can use [react-native-dotenv](https://www.npmjs.com/package/react-native-dotenv) to configure environment variables: + +```sh +TOLGEE_API_URL=https://app.tolgee.io +TOLGEE_API_KEY=tgpak_gfpwiojtnrztqmtbna3dczjxny2ha3dmnu4tk4tnnjvgc +``` + +> Obtaining Tolgee API key is described in [Integration](/platform/integrations/about_integrations) chapter. + +:::info +In-context tools won't have any effect in emulators, but can help you when you run the app in web mode. +::: + +## Production + +Make sure you don't include `TOLGEE_API_KEY` in production build. \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/ssr.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/ssr.mdx new file mode 100644 index 000000000..5b2cc495c --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/ssr.mdx @@ -0,0 +1,43 @@ +--- +id: ssr +title: SSR support +sidebar_label: SSR support +--- + +Since version `SDK v5.30.0`, TolgeeProvider has `ssr` property, where you can pass `language` and optionally `staticData`. TolgeeProvider will then ensure, that tolgee instance is switched to this language for the first render immediately and loads static data directly into cache. Everything is then prepared for SSR rendering. + +Loading correct locale and static data is a operation that needs to be performed on the server and it's implementation depends on used framework. + +```jsx +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + defaultLanguage: 'en', + }) + +const App = () => { + // this needs to work on server + const language = ? + const staticData = ? + + return ( + + ... + + ) +} +``` + +## Language changing + +When we use SSR, we have to specify language in a way that is detectable by both client and server. There are basically two ways - either use cookie or include locale information directly in the url. + +Then for language change we use the native way of the framework. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/switching_languages.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/switching_languages.mdx new file mode 100644 index 000000000..226bd160b --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/switching_languages.mdx @@ -0,0 +1,31 @@ +--- +id: switching_language +title: Switching Language +sidebar_label: Switching language +slug: switching-language +description: "Tolgee enables you to implement localization in your React application with ease. You can integrate Tolgee in your React application using Tolgee's SDK for React. Learn to manage language change using the useTolgee hook." +--- + +To manage an event like language change, the React SDK provides the [`useTolgee`](./api#hook-usetolgee) hook. + +Pass the [`language`](/api/core_package/events.mdx) event to the hook to re-render the component when the event emits. + +```jsx +import { useTolgee } from '@tolgee/react'; + +export const LangSelector = () => { + const tolgee = useTolgee(['language']); + + return ( + + ); +}; +``` + +> Learn more about language change, detection and storage on the [Language documentation page](../../language). diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/tags_interpolation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/tags_interpolation.mdx new file mode 100644 index 000000000..0e1a01766 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/tags_interpolation.mdx @@ -0,0 +1,65 @@ +--- +id: tags_interpolation +title: Tags Interpolation +sidebar_label: Tags interpolation +slug: tags-interpolation +description: "Tolgee enables you to implement localization in your React application with ease. You can integrate Tolgee in your React application using Tolgee's SDK for React. Moreover, the SDK supports Tags Interpolation. Learn more about Tags Interpolation and how to use them." +--- + +You might want to add formatting to certain part of the translated string. For example, let's take the translated string `Tolgee and React work well`. You want to add italics to the word `Tolgee` and bold to the word `React`. Using the React SDK, you can implement this easily. + +The React SDK, uses [`FormatIcu`](/formatting.mdx#icu-formatter) which supports tags interpolation. You can add custom tags in translations and map them to React elements to implement such formatting. + +There are two different ways to implement tags interpolation. + +## 1. Passing an element + +:::info +Currently, non-closing tags and self-closing tags are not supported [#3101](https://github.com/tolgee/tolgee-js/issues/3101). +::: + +### Example usage + +```jsx +import { FormatIcu } from '@tolgee/format-icu'; + +... + +tolgee.use(FormatIcu()) +``` + +```jsx +const Component = () => { + return ( +
+ }} + defaultValue="This is formatted" + /> +
+ ); +}; +``` + +The above example code will render the following text. + +![Formatted text](/img/docs/text_formatted.png) + +## 2. Passing a function + +### Example usage + +You can also pass a function that returns a React node. This function can take `content` as an argument such that `(content: React.ReactNode) => React.ReactNode` + +```tsx +const myFunction = (content) => {content}; + +; +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/react/translating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/react/translating.mdx new file mode 100644 index 000000000..baeb7dfdf --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/react/translating.mdx @@ -0,0 +1,64 @@ +--- +id: translating +title: Translating +sidebar_label: Translating +description: "Tolgee enables you to implement localization in your React application with ease. You can integrate Tolgee in your React application using Tolgee's SDK for React. Learn to impelement translation using the useTranslate hook and the T-component." +--- + +import TFunctionHint from '../../shared/_TFunctionHint.mdx'; +import NamespacesHint from '../../shared/_NamespacesHint.mdx'; + +You can implement translation in your React application in two ways. + +- Using the [useTranslate hook](#usetranslate-hook) +- Using the [T-component](#t-component) + +## `useTranslate` hook + +The Tolgee React SDK provides the [`useTranslate`](./api#hook-usetranslate) hook, which comes with a translation function ([`t` function](./api#function-t)). Using this `t` function, you can render the actual translation. + +```js +import { useTranslate } from '@tolgee/react'; + +function Component() { + const { t } = useTranslate(); + + return
{t('key_to_translate', 'DEFAULT VALUE')}
; +} +``` + + + +## `T` component + +An alternative way is to use the [`T` component](./api#t-component). It has a very similar API to the `t` function mentioned above. + +```jsx +import { T } from "@tolgee/react"; + +... + + +``` + +## useTranslate hook vs T-component + +The `T` component has a similar API to that of the `t` function mentioned above. However, there are some considerations when deciding which to use: + +- To apply translation in the component where you don't have access to React hooks, use the `T` component. +- If you want to use [Tags interpolation](./tags-interpolation), use the `T` component. +- For namespaces, use the `useTranslate` hook. Check the following example to learn more: + +```ts +const { t, isLoading } = useTranslate('common'); +``` + + + +import ExampleBanner from '../../shared/ExampleBanner'; + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/api.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/api.mdx new file mode 100644 index 000000000..b27305c32 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/api.mdx @@ -0,0 +1,128 @@ +--- +id: api +title: API +sidebar_label: API +description: "Explore Tolgee's Svelte API for localization. Learn about TolgeeProvider, T component, getTranslate and getTolgee functions. Includes props, parameters, and usage examples for efficient translation implementation." +--- + +## `TolgeeProvider` + +Provides the Tolgee context. Use in the root of the application. + +```jsx +import { TolgeeProvider } from '@tolgee/svelte'; + +... + + + + +``` + +### Prop `fallback?` + +`string` - rendered when translations are loading. + +### Prop `tolgee?` + +Provide a Tolgee instance. + +### Slot `fallback` + +Alternative for `fallback`. Use when you to pass components. + +```svelte + + +
Loading...
+
+``` + +## `T` component + +Implement translation in your Svelte application. It has a very similar API to the `t` function. + +```svelte +import { T } from '@tolgee/svelte'; + + +``` + +### Prop `keyName` + +`String` - translation key. + +### Prop `defaultValue?` + +`String` - Rendered if no translation for the specified key (in neither the selected language nor the base language) is present. If not provided, `keyName` gets rendered instead. + +### Prop `params?` + +`Record` - variable params, which can be used in translation value +(read more about [ICU message format](/platform/translation_process/icu_message_format)). + +### Prop `ns?` + +`string` - translation namespace + +### Prop `noWrap?` + +`Boolean` (default: `false`) + +- `false` - in development mode translation will be [wrapped](/wrapping.mdx) +- `true` - use when wrapping in dev mode causes problems. In-context translation won't work. + +### Prop `language?` + +`String` - override current Tolgee language. Allows switching to different language for separate translation. Load the language manually with [`tolgee.loadRecord`](/api/core_package/tolgee.mdx#loadrecord). + +## Function `getTranslate` + +Use it for loading namespaces for specific components/pages or to get `t` function for translating (returned `t` function uses first namespace from the list automatically). + +```ts +function getTranslate(ns?: string | string[]): { + t: Readable; + isLoading: Readable; +}; +``` + +### Parameter `ns` + +- `string` | `string[]` - namespace(s) to be loaded + +### Property `isLoading` (Readable) + +- `boolean` - is true if any of the listed namespaces is loading. Use this property to ensure translations are rendered after they are loaded. + +### Function `t` (Readable) + +Returns requested translation and also subscribes component to translation/language changes. Component will be re-rendered every time translation changes. If used with namespaces, `t` function will automatically use the first the namespace given to `useTranslate` function. Override this with the `ns` option. + +```ts +$t('key', 'Default value', ) +``` + +> Check [`tolgee.t`](/api/core_package/tolgee.mdx#t) function interface. + +## Function `getTolgee` + +Returns the Tolgee instance as a `Readable`. Allows subscription to different [`events`](/api/core_package/events.mdx). Most common usecase is for language switching. + +```svelte + + +
Language: {$tolgee1.getLanguage()}
+
Loading: {$tolgee2.isLoading()}
+``` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/installation.mdx new file mode 100644 index 000000000..30b40019e --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/installation.mdx @@ -0,0 +1,83 @@ +--- +id: installation +title: Installation +sidebar_label: Installation +description: "Learn how to install and configure Tolgee for Svelte localization. Step-by-step guide covers library installation, environment setup, TolgeeProvider configuration, and production preparation. Includes code examples." +--- + +import ExampleBanner from '../../shared/ExampleBanner'; +import PreparingForProduction from '../../shared/_PreparingForProduction.mdx'; +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; + +To use Tolgee in your React application, you need to follow these steps: + +1. [Install the Tolgee integration library](#install-the-tolgee-integration-library) +2. [Set up the environment variables](#set-up-the-environment-variables) +3. [Configure the TolgeeProvider](#configure-the-tolgeeprovider) +4. [Preparing for production](#preparing-for-production) + +## Install the Tolgee integration library + +To install Tolgee Svelte integration library, run the following command. + + + +## Set up the environment variables + + +Once the library is installed, you need to initialize it. For initialization, you need the Tolgee API URL, and the Tolgee API Key. To generate the API Key, follow the step-by-step instructions mentioned on the [API keys and PAT tokens page](../../../../../platform/account_settings/api_keys_and_pat_tokens). + +:::danger +Make sure you don't leak your API key. If the API key is leaked, visitors can edit the translations on your site. +::: + +After generating the API key, add these credentials. If you bootstrapped the application with SvelteKit, add them to the `.env.development.local` file. Your `.env.development.local` should look as follow. + +```env +VITE_TOLGEE_API_KEY=tgpak_gfpwiojtnrztqmtbna3dczjxny2ha3dmnu4tk4tnnjvgc +VITE_TOLGEE_API_URL=https://app.tolgee.io +``` + +## Configure the TolgeeProvider + +Next, initialize Tolgee and wrap the application in [`TolgeeProvider`](./api#tolgeeprovider). + +```svelte + + + +
Loading...
+ +
+``` +The above code does the following: + +- Imports the required methods and plugins from the integration library. +- Configures the Tolgee instance to use the [DevTools](../../api/web_package/plugins#devtools) and [FormatSimple](../../api/core_package/format-simple) plugins, and initializes it using the credentials. It also sets the language to English. +- Wraps a component within the `TolgeeProvider`. + +> You can configure more options and plugins during initialization. Learn about other [options](../../api/core_package/options) and [Tolgee plugins](../../plugins) in their respective documentation. + +## Preparing for production + + + + diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/overview.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/overview.mdx new file mode 100644 index 000000000..18baaab23 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/overview.mdx @@ -0,0 +1,11 @@ +--- +id: overview +title: Tolgee with Svelte +sidebar_label: Overview +description: "Implement localization in your Svelte app with Tolgee. Features include in-context translations and language detection. Get started with our installation guide and examples." +--- + +Tolgee provides an integration library for Svelte, based on the [`@tolgee/web`](../../api/web_package/about) package. Using the [@tolgee/svelte](https://www.npmjs.com/package/@tolgee/svelte) library, you can connect your Svelte application to the Tolgee platform. This library enables you to implement [in-context translations](../../in-context), [language detection](../../language#language-detection), interpolation, and other advanced features. + +- Follow the [installation guide](./installation) to add the Tolgee Svelte integration library to your existing Svelte application. +- You can also check the [example application](https://github.com/tolgee/svelte-example) to see how to integrate Tolgee with Svelte. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/switching_language.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/switching_language.mdx new file mode 100644 index 000000000..b7cd853f3 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/switching_language.mdx @@ -0,0 +1,27 @@ +--- +id: switching_language +title: Switching language +sidebar_label: Switching language +slug: switching-language +description: "Learn to manage language switching in Svelte apps using Tolgee. Guide demonstrates how to use the getTolgee function and subscribe to language events, with code examples for implementation." +--- + +For language management, the Tolgee integration provides the [`getTolgee`](./api#function-gettolgee) function. Use this function with subscription to the `language` event. Below is an example of how to use it for switching languages. + +```svelte + + + +``` + +> Read more about [Language change, detection and storage](/language.mdx) diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/translating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/translating.mdx new file mode 100644 index 000000000..52ff07432 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/svelte/translating.mdx @@ -0,0 +1,63 @@ +--- +id: translating +title: Translating +sidebar_label: Translating +description: "Learn to implement translations in Svelte using Tolgee. Guide covers the getTranslate function and T component, with examples for basic usage and advanced features like namespaces and parameter interpolation." +--- + +import TFunctionHint from '../../shared/_TFunctionHint.mdx'; +import NamespacesHint from '../../shared/_NamespacesHint.mdx'; +import ExampleBanner from '../../shared/ExampleBanner'; + +You can implement translation in your Svelte application in the following ways. + +- Using the [`getTranslate` function](#gettranslate-function) +- Using the [`T` component](#the-t-component) + +## `getTranslate` function + +The [`getTranslate` function](./api#function-gettranslate) provides a `t` function. This `t` function should be used to render the respective translation. + +The below code demonstrates the implemention of the `getTranslate` and the `t` function. + +```svelte + + + +{$t('key', 'Default value')} +``` + + + +### Using namespaces + +Moreover, you can load namespaces with the `getTranslate` function. + +```ts +const { t, isLoading } = getTranslate('common'); +``` + + + +## The `T` component + +You can also use the [`T` component](./api#t-component) for translation. It has an API similar to the `t` function mentioned above. + +```svelte + + + +``` + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/initialization.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/initialization.mdx new file mode 100644 index 000000000..726ebd839 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/initialization.mdx @@ -0,0 +1,53 @@ +--- +id: initialization +title: Initialization +description: 'Tolgee initialization' +--- + +Standard way to initialize Tolgee is: + +```ts +import {Tolgee, DevTools, FormatSimple} from '@tolgee/web' + +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + language: 'en' + + // for development mode + apiUrl: 'https://app.tolgee.io' + apiKey: '' + + // for production + staticData: { + ... + } + }) + +// this part starts loading data +tolgee.run(); +``` + +:::warning +Never leak your API key! We strongly recommend against adding API key into git repository. +::: + +We use `DevTools` plugin which will provide in-context translating in dev mode. We also need to provide `apiUrl` and `apiKey` for the in-context to work. + +> Obtaining Tolgee API key is described in [Integration](/platform/integrations/about_integrations) chapter. + +> Check all [tolgee options](/api/core_package/options.mdx) and [tolgee plugins](/api/web_package/plugins.mdx). + +> Tolgee SDK can be used [without connection to Tolgee Platform](/usage_without_platform.mdx) + +## What does `run` do? + +:::info +In tolgee integrations `run` method is usually called automatically in tolgee provider, it is generally better to hook it into framework lifecycle because of SSR. +::: + +Tolgee [`run`](../../api/core_package/tolgee.mdx#run) method will start loading translations data (if necessary) and also activates in-context tools. + +You can use tolgee without running it (e.g. on server with ssr). That way tolgee won't activate in-context tools and will not load anything asynchronously, but you can still use [`tolgee.t`](../../api/core_package/tolgee.mdx#t) function with [`staticData`](../../api/core_package/options#staticdata). diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/installation.mdx new file mode 100644 index 000000000..6eff84db6 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/installation.mdx @@ -0,0 +1,58 @@ +--- +id: installation +title: Installation +description: 'Explore multiple ways how to install Tolgee: with supported integrations, using dev tools without bundler and more.' +--- + +If you use vanilla javascript or an unsupported framework, use [`@tolgee/web`](../../api/web_package/about) package. + +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; + + + +```ts +import { Tolgee } from '@tolgee/web'; +``` + +### With script tag + +```jsx + +``` + +```ts +const { Tolgee } = window['@tolgee/web']; +``` + +### With native es import + +```ts +import { Tolgee } from 'https://cdn.jsdelivr.net/npm/@tolgee/web/dist/tolgee-web.production.esm.min.mjs' +``` + +## In-context (DevTools) + +To use In-context in development mode, you can simply use [`DevTools`](../../api/web_package/plugins#devtools) from `@tolgee/web` + +```ts +import { Tolgee, DevTools } from '@tolgee/web'; + +const tolgee = Tolgee().use(DevTools()).init(...) +``` + +If you use any standard FE setup with a bundler, `DevTools` will be automatically excluded in production (based on `process.env.NODE_ENV`). If you want the in-context to be always available, use [`InContextTools`](/api/web_package/tools.mdx#incontexttools) instead of `DevTools`- it is the same plugin, the only difference is that `DevTools` are exported conditionally. + +## Using dev tools without bundler + +You can use separately bundled `in-context-tools` however, you'll have to make sure you won't include it in production. + +```jsx +; +const { InContextTools } = window['@tolgee/in-context-tools']; + +// or + +import { InContextTools } from 'https://cdn.jsdelivr.net/npm/@tolgee/web/dist/tolgee-in-context-tools.esm.min.mjs'; +``` + +Check the [complete list](https://cdn.jsdelivr.net/npm/@tolgee/web/dist/) of bundles. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/translating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/translating.mdx new file mode 100644 index 000000000..262b56ecb --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vanilla/translating.mdx @@ -0,0 +1,37 @@ +--- +id: translating +title: Translating +description: 'Tolgee translating with vanilla JS.' +--- + +Use [`tolgee.t`](../../api/core_package/tolgee.mdx#t) method to translate keys. + +```ts +domElement.textContent = tolgee.t('key', 'Default value'); +``` + +However this will not update translation in case you change language or update translation through in-context. + +### Listening for changes + +:::info +If you use Tolgee with any supported framework, this is solved for you automatically. +::: + +You can [listen](../../api/core_package/tolgee.mdx#on) for translation change with `update` event. + +```ts +tolgee.on('update', () => { + domElement.textContent = tolgee.t('key', 'Default value'); +}); +``` + +This event gets triggered when any translation need to be updated, so it might be inefficient if you use many namespaces. Therefore there is also [`onNsUpdate`](../../api/core_package/tolgee.mdx#onnsupdate) method, which allows you to subscribe only to selected namespaces. + +```ts +tolgee + .onNsUpdate(() => { + domElement.textContent = tolgee.t('key', 'Default value'); + }) + .subscribeNs('my_namespace'); +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/api.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/api.mdx new file mode 100644 index 000000000..3171dc30e --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/api.mdx @@ -0,0 +1,189 @@ +--- +id: api +title: API +sidebar_label: API +description: "Explore Tolgee's Vue API for easy localization. Learn about VueTolgee plugin, TolgeeProvider, T component, and composables. Includes usage examples and prop descriptions for efficient integration." +--- + +The `@tolgee/vue` library exports the following components and all available components from [`@tolgee/web`](/api/web_package/about.mdx). + +## VueTolgee plugin + +`VueTolgee` plugin attaches Tolgee to a Vue instance. This allows, you to use the `$t` function in any Vue component. + +```jsx +import { VueTolgee } from '@tolgee/vue'; + +... + +app.use(VueTolgee, { tolgee }); + +... + +// A child Vue component +
+ {{ $t('key', 'Default value') }} +
+``` + +> For more refer to the [`tolgee.t`](/api/core_package/tolgee.mdx#t) function interface. + +You can also integrate Tolgee without using the `VueTolgee` plugin. In this case use the `TolgeeProvider` and the `useTranslate` method for translating. + +## `TolgeeProvider` + +Provides the Tolgee context. Use in root of the application. + +```jsx +import { TolgeeProvider } from '@tolgee/vue'; + +... + + + + +``` + +### Prop `fallback?` + +`String | JSX.Element` - rendered when Tolgee is loading translations instead of provided content. +Pass custom loading element when using Vue with JSX. + +```html + + + +``` + +### Slot `fallback?` + +Alternative for `fallback`. Use when you are using Vue with templates: + +```html + + + + +``` + +### Prop `tolgee?` + +Provide a Tolgee instance. If you use `VueTolgee`, this is not necessary. + +## `T` component + +Implement translation in your Vue application. It has a very similar API to the `t` function. + +```js +import { T } from '@tolgee/vue'; +``` + +```html + +``` + +### Prop `keyName` + +`String` - translation key. + +### Prop `defaultValue?` + +`String` - Rendered if no translation for the specified key (in neither the selected language nor the base language) is present. If not provided, `keyName` gets rendered instead. + +### Prop `params?` + +`Record` - variable params, which can be used in translation value +(read more about [ICU message format](/platform/translation_process/icu_message_format)). + +### Prop `ns?` + +`string` - translation namespace + +### Prop `noWrap?` + +`Boolean` (default: `false`) + +- `false` - in development mode translation will be [wrapped](/wrapping.mdx) +- `true` - use when wrapping in dev mode causes problems. In-context translation won't work. + +### Prop `language?` + +`String` - override current Tolgee language. Allows switching to different language for separate translation. Load the language manually with [`tolgee.loadRecord`](/api/core_package/tolgee.mdx#loadrecord). + +### Children + +:::info +Feature available from version 5.24.0 +::: + +Pass children to the `T` component that gets mapped to the translation parameters respective to their slot names. Allows implementation of custom components as a part of the translation. + +```html + +``` + +## Composable `useTranslate` + +Use it for loading namespaces for specific components/pages or to get `t` function for translating (returned `t` function uses first namespace from the list automatically). + +```ts +function useTranslate(ns?: string | string[]): { + t: Ref; + isLoading: Ref; +}; +``` + +### Parameter `ns` + +- `string` | `string[]` - namespace(s) which will be loaded + +### Property `isLoading` (Ref) + +- `boolean` - is true if any of listed namespaces is not loaded yet + +Use this property to ensure translations are rendered after they are loaded. + +### Function `t` (Ref) + +Returns requested translation and also subscribes component to translation/language changes. Component will be re-rendered every time translation changes. If used with namespaces, `t` function will automatically use the first the namespace given to `useTranslate` function. Override this with the `ns` option. + +```ts +t('key', 'Default value', ); +``` + +> Check [`tolgee.t`](/api/core_package/tolgee.mdx#t) function interface. + +## Composable `useTolgee` + +Returns Tolgee instance as a `Ref`. Allows subscription to different [`events`](/api/core_package/events.mdx), which will trigger re-render. Most common usecase is for language switching. + +```html + + + +``` diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/component_interpolation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/component_interpolation.mdx new file mode 100644 index 000000000..14572d6a5 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/component_interpolation.mdx @@ -0,0 +1,57 @@ +--- +id: component_interpolation +title: Component interpolation +sidebar_label: Component interpolation +slug: component-interpolation +description: "Learn to use component interpolation in Vue with Tolgee. Guide covers implementing custom components within translations, using FormatIcu and the T component. Includes code examples and usage tips." +--- + +:::info +Feature available from version 5.24.0 +::: + +You might want to use a Component for certain part of the translated string. For example, let's take the translated string `Click this link`. You want to add the anchor tag to the word `link`. Using the integration library, you can implement this easily. + +The integration library, uses [`FormatIcu`](/formatting.mdx#icu-formatter) which supports component interpolation. You can map variables in translations to custom Vue components. + +:::info +Currently, non-closing tags and self-closing tags are not supported [#3101](https://github.com/tolgee/tolgee-js/issues/3101). +::: + +### Example usage + +```jsx +import { FormatIcu } from '@tolgee/format-icu'; + +... + +tolgee.use(FormatIcu()) +``` + +The above code configures Tolgee to use the `FormatIcu` formatter. + +Next, use the [`T` component](./translating#t-component) with named slots. These are mapped to the variables in the translation. + +```html + + + +``` + + +The above example code will provide the following output. + +```html +Click this link +``` \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/installation.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/installation.mdx new file mode 100644 index 000000000..bf13e2eea --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/installation.mdx @@ -0,0 +1,100 @@ +--- +id: installation +title: Installation +sidebar_label: Installation +description: "Step-by-step guide to install Tolgee in Vue apps. Covers library installation, environment setup, Tolgee initialization, and TolgeeProvider integration. Includes production preparation tips and code examples." +--- + +import ExampleBanner from '../../shared/ExampleBanner'; +import { InstallationTabs } from '../../../../src/component/InstallationTabs'; +import PreparingForProduction from '../../shared/_PreparingForProduction.mdx'; + +To use Tolgee in your Vue application, you need to follow these steps: + +1. [Install the Tolgee integration library](#install-the-tolgee-integration-library) +2. [Set up the environment variables](#set-up-the-environment-variables) +3. [Initialize Tolgee](#initialize-tolgee) +4. [Add the Tolgee Provider](#add-the-tolgee-provider) +5. [Preparing for production](#preparing-for-production) + + +## Install the Tolgee integration library + +To install Tolgee integration library, run the following command: + + + +## Set up the environment variables + +Once the library is installed, you need to initialize it. For initialization, you need the Tolgee API URL, and the Tolgee API Key. To generate the API Key, follow the step-by-step instructions mentioned on the [API keys and PAT tokens page](../../../../../platform/account_settings/api_keys_and_pat_tokens). + +:::danger +Make sure you don't leak your API key. If the API key is leaked, visitors can edit the translations on your site. +::: + +After generating the API key, add these credentials. If you bootstrapped the Vue application using the vue-cli, add them to the `.env.development.local` file. Your `.env.development.local` should look like this: + +```env +VITE_APP_TOLGEE_API_URL=https://app.tolgee.io +VITE_APP_TOLGEE_API_KEY=tgpak_gfpwiojtnrztqmtbna3dczjxny2ha3dmnu4tk4tnnjvgc +``` + +Otherwise, you can set these properties directly, or use plugins like [dotenv-webpack](https://www.npmjs.com/package/dotenv-webpack). + +## Initialize Tolgee + +Next initialize `Tolgee` and use [`VueTolgee`](./api#vuetolgee-plugin). The `VueTolgee` plugin makes [`$t`](./translating#global-t-function) function globally available. + +```ts +import { Tolgee, DevTools, VueTolgee, FormatSimple } from '@tolgee/vue'; + +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + .init({ + language: 'en', + + // for development + apiUrl: import.meta.env.VITE_APP_TOLGEE_API_URL, + apiKey: import.meta.env.VITE_APP_TOLGEE_API_KEY, + + // for production + staticData: { + ... + } + }); + +... + +app.use(VueTolgee, { tolgee }); +``` + +The above code does the following: + +- Imports the required classes, and plugins from the integration library. +- Configures the Tolgee instance to use the [DevTools](../../api/web_package/plugins#devtools) and [FormatSimple](../../api/core_package/format-simple) plugins, and initializes it using the credentials. It also sets the language to English. +- Registers the `VueTolgee` plugin with the Vue application. + +> You can configure more options and plugins during initialization. Learn about these other [options](../../api/core_package/options) and [Tolgee plugins](../../plugins) in their respective documentation. + +## Add the Tolgee Provider + +Wrap your application with the [`TolgeeProvider`](./api#tolgeeprovider) component. `TolgeeProvider` will initialize tolgee when it is ready and waits for initial data to be load. You can also provide a `fallback` slot to show custom loader while translations are being loaded. + +```html + +``` + +## Preparing for production + + + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/overview.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/overview.mdx new file mode 100644 index 000000000..3d3cc5ce6 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/overview.mdx @@ -0,0 +1,11 @@ +--- +id: overview +title: Tolgee with Vue +sidebar_label: Overview +description: "Discover Tolgee's Vue integration for localization. Learn to implement in-context translations, language detection, and more. Includes installation guide and example app for easy Vue integration." +--- + +Tolgee provides an SDK for Vue, based on the [`@tolgee/web`](../../api/web_package/about) package. Using the SDK [@tolgee/vue](https://www.npmjs.com/package/@tolgee/vue), you can connect your Vue application to the Tolgee platform. This SDK enables you to implement [in-context translations](../../in-context), [language detection](../../language#language-detection), interpolation, and other advanced features. + +- Follow the [installation guide](./installation) to add the Tolgee Vue SDK to your existing Vue application. +- You can also check the [example application](https://github.com/tolgee/vue-example) to see how to integrate Tolgee with Vue. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/ssr.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/ssr.mdx new file mode 100644 index 000000000..7d5c811c1 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/ssr.mdx @@ -0,0 +1,79 @@ +--- +id: ssr +title: SSR support +sidebar_label: SSR support +--- + +To use Tolgee with your SSR framework such as Vike (vite-plugin-ssr), you can provide localization data imported as object +using `staticData` props of TolgeeProvider component, this data will be loaded directly into cache and immediately +available with first render for SSR. We also need to know user's locale already on the server and set it through TolgeeProvider `language` prop. + +```ts +import { Tolgee, DevTools, VueTolgee, FormatSimple } from '@tolgee/vue'; + +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + .init({ + defaultLanguage: 'en', + }); + +... + +app.use(VueTolgee, { tolgee, enableSSR: true }); +``` + +and in the your root component + +```html + + +... + + +``` + +With this approach we include all translations directly in the bundle regardless user locale. For smaller projects this is not a big issue, however it might be significant for large applications with many translations and languages. + +For these cases we need to only provide statically the locale, that the user is currently using. We can also use async functions in `staticData` which will be used for fetching translations dynamically on client side (you can use this instead of having them in public folder). + +```html + + +... + + +``` + +## Language changing + +When we use SSR, we have to specify language in a way that is detectable by both client and server. Easiest way is to +include it directly in URL. + +Then for language change we use the native way of the framework. diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/switching_languages.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/switching_languages.mdx new file mode 100644 index 000000000..3c1bfbad3 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/switching_languages.mdx @@ -0,0 +1,29 @@ +--- +id: switching_language +title: Switching language +sidebar_label: Switching language +slug: switching-language +description: "Learn to manage language switching in Vue apps using Tolgee. Guide demonstrates how to use the useTolgee composable function and handle language events, with code examples for implementation." +--- + +For language management, the Tolgee integration provides the [`useTolgee`](./api#composable-usetolgee) composable function. Use this function with subscription to the `language` event. Below is an example of how to use it for switching languages. + +```html + + + +``` + +> Read more about [Language change, detection and storage](/language.mdx) \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/integrations/vue/translating.mdx b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/translating.mdx new file mode 100644 index 000000000..db324daae --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/integrations/vue/translating.mdx @@ -0,0 +1,75 @@ +--- +id: translating +title: Translating +sidebar_label: Translating +description: "Learn to implement translations in Vue using Tolgee. Guide covers global $t function, useTranslate composable, and T component. Includes examples for basic usage, namespaces, and parameter interpolation." +--- + +import TFunctionHint from '../../shared/_TFunctionHint.mdx'; +import NamespacesHint from '../../shared/_NamespacesHint.mdx'; +import ExampleBanner from '../../shared/ExampleBanner'; + + +You can implement translation in your Vue application in the following ways. + +- Using the [global `$t` function](##global-t-function) +- Using the [`useTranslate` composable](#usetranslate-composable) +- Using the [`T` component](#the-t-component) + +## Global `$t` function + +The [`VueTolgee`](./api#vuetolgee-plugin) plugin provides a `$t` function that allows you to translate strings in your Vue application. If the `VueTolgee` plugin is registered, the `$t` function is available globally in your application. + +```html + +``` + +## `useTranslate` composable + +The Tolgee integration library provides the [`useTranslate`](./api#composable-usetranslate) composable, which comes with a translation function ([`t` function](./api#function-t-ref)). Using this `t` function, you can render the actual translation. + +```html + + + +``` + + + +### Using namespaces + +The `useTranslate` composable can be also used for loading additional namespaces. + +```ts +const { t, isLoading } = useTranslate('common'); +``` + + + +## The `T` component + +An alternative way is to use the [`T` component](./api#t-component). It has a very similar API to the `t` function mentioned above. + +```html + + + +``` + + \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/keys_tagging.mdx b/js-sdk_versioned_docs/version-5.x.x/keys_tagging.mdx new file mode 100644 index 000000000..c7f19286d --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/keys_tagging.mdx @@ -0,0 +1,23 @@ +--- +id: keys_tagging +title: Tagging new keys +description: 'Automatical pre-selecting tags for newly created keys.' +--- + +import { ScreenshotWrapper } from '../../platform/shared/_ScreenshotWrapper'; + + +For larger projects, it might be beneficial to keep track which keys are newly created and which are already used on production. + +Tolgee SDK offers a simple option to preselect a tag when creating new key through in-context UI. + +```ts +tolgee.init({ + ... + tagNewKeys: ['draft'] +}) +``` + +This will result in `draft` tag being preselected in In-context translating UI: + +![In-context tools with draft tag preselected](/img/docs/sdk/draft-tag.webp) \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/language.mdx b/js-sdk_versioned_docs/version-5.x.x/language.mdx new file mode 100644 index 000000000..377437b47 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/language.mdx @@ -0,0 +1,52 @@ +--- +id: language +title: Language +description: 'Language of the application UI with Tolgee. To change language use changeLanguage method.' +--- + +To change language use [`changeLanguage`](./api/core_package/tolgee.mdx#changelanguage) method. + +```ts +await tolgee.changeLanguage('es'); +``` + +## Getting current language + +When Tolgee is running, this will first change `pendingLanguage` property, loads required translation file(s) (when they are not already in the cache) and then changes `language` property. + +Use [`getLanguage`](./api/core_package/tolgee.mdx#getlanguage) or [`getPendingLanguage`](./api/core_package/tolgee.mdx#getpendinglanguage) methods to read current state. + +You can also listen to `language` and `pendingLanguage` [events](./api/core_package/events). + +> When Tolgee is not running, `changeLanguage` won't fetch translations, it only changes the internal state + +## Language detection + +Tolgee includes [`LanguageDetector`](./api/web_package/plugins#languagedetector) plugin, which tries to detect language from browser `window.navigator.language` property. + +If you use language detector Tolgee needs to know available languages which you can provide with `availableLanguages` property or if you provide `staticData` it will be taken from there. You also have to provide `defaultLanguage` property, so Tolgee knows which language should be picked if detection fails. + +```ts +const tolgee = Tolgee() + .use(LanguageDetector()) + .init({ + availableLanguages: ['en', 'cs', 'es', 'fr'], + defaultLanguage: 'en' + }) +``` + +## Language storage + +Once user selects his language Tolgee can remember it for the next time. Use [`LanguageStorage`](./api/web_package/plugins#languagestorage) plugin, for storing language settings in the browser local storage. + +Tolgee checks if the stored language is valid and because of that you need to specify `availableLanguages` or define language data through `staticData`. + +```ts +const tolgee = Tolgee() + .use(LanguageStorage()) + .init({ + availableLanguages: ['en', 'cs', 'es', 'fr'], + }) +``` + +> Read more about [Tolgee Plugins](./plugins.mdx). diff --git a/js-sdk/migration_to_v5/core.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/core.mdx similarity index 100% rename from js-sdk/migration_to_v5/core.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/core.mdx diff --git a/js-sdk/migration_to_v5/i18next.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/i18next.mdx similarity index 100% rename from js-sdk/migration_to_v5/i18next.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/i18next.mdx diff --git a/js-sdk/migration_to_v5/ngx.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/ngx.mdx similarity index 100% rename from js-sdk/migration_to_v5/ngx.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/ngx.mdx diff --git a/js-sdk/migration_to_v5/react.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/react.mdx similarity index 100% rename from js-sdk/migration_to_v5/react.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/react.mdx diff --git a/js-sdk/migration_to_v5/svelte.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/svelte.mdx similarity index 100% rename from js-sdk/migration_to_v5/svelte.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/svelte.mdx diff --git a/js-sdk/migration_to_v5/vue.mdx b/js-sdk_versioned_docs/version-5.x.x/migration_to_v5/vue.mdx similarity index 100% rename from js-sdk/migration_to_v5/vue.mdx rename to js-sdk_versioned_docs/version-5.x.x/migration_to_v5/vue.mdx diff --git a/js-sdk_versioned_docs/version-5.x.x/namespaces.mdx b/js-sdk_versioned_docs/version-5.x.x/namespaces.mdx new file mode 100644 index 000000000..567af8755 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/namespaces.mdx @@ -0,0 +1,48 @@ +--- +id: namespaces +title: Namespaces +description: 'Tolgee namespaces with vanilla JS. Namespaces are a way how to separate translations into multiple files, which can be downloaded only when needed.' +--- + +Tolgee namespaces are a way how to separate translations into multiple files, which can be downloaded only when needed. By default `Tolgee` uses "" (empty string) namespace. You can change this through initial [`Options`](./api/core_package/options). + +```ts +const tolgee = Tolgee().init({ + // set default namespace + defaultNs: 'common', + + // define which namespaces should be fetched initially + ns: ['common', 'test'], +}); +``` + +## Definition through `staticData` + +You can tell Tolgee how to get namespaces through [`staticData`](./api/core_package/options#staticdata), ideally with async function: + +```ts +const tolgee = Tolgee().init({ + staticData: { + 'en:common': () => import('./i18n/common/en.json'), + 'en:test': () => import('./i18n/test/en.json'), + }, +}); +``` + +You can also use plugins for loading namespaces check [Providing static data](./providing-static-data). + +## Active namespaces + +To download new namespaces dynamically you can change active namespaces with [`addActiveNs`](./api/core_package/tolgee.mdx#addactivens) method. Active namespaces are automatically fetched and also when you change the languge (when tolgee is running). + +```ts +tolgee.addActiveNs('newNamespace'); +``` + +Removing active namespace: + +```ts +tolgee.removeActiveNs('newNamespace'); +``` + +Tolgee internally rembers how many times is each namespace used and removed, so it will only remove namespace if [`removeActiveNs`](./api/core_package/tolgee.mdx#removeactivens) was called the same time as [`addActiveNs`](./api/core_package/tolgee.mdx#addactivens). diff --git a/js-sdk_versioned_docs/version-5.x.x/plugins.mdx b/js-sdk_versioned_docs/version-5.x.x/plugins.mdx new file mode 100644 index 000000000..9855e894f --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/plugins.mdx @@ -0,0 +1,30 @@ +--- +id: plugins +title: Plugins +sidebar_label: Plugins +description: 'Plugins of the application UI with Tolgee in Vanilla JS.' +--- + +For adding further functionality Tolgee SDK contains a plugin system. You can add them at the Tolgee initialization: + +```ts +const tolgee = Tolgee() + .use(DevTools()) // <-– plugin + .use(FormatSimple()) // <-- another plugin + .init(...) +``` + +## Existing plugins + - [DevTools](./api/web_package/plugins#devtools) - for in-context translating in dev mode + +#### Formatters + - [FormatSimple](./api/core_package/format-simple) - bundle size friendly simple formatter + - [FormatIcu](./formatting#icu-formatter) - full ICU formatter + +#### Backends (data fetching) + - [BackendFetch](./api/web_package/plugins#backendfetch) - fetch data from custom backend + +#### Language helpers + - [LanguageStorage](./api/web_package/plugins#languagestorage) - store language in LocalStorage + - [LanguageDetector](./api/web_package/plugins#languagedetector) - detect user language from browser + diff --git a/js-sdk_versioned_docs/version-5.x.x/providing_static_data.mdx b/js-sdk_versioned_docs/version-5.x.x/providing_static_data.mdx new file mode 100644 index 000000000..3caae8d67 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/providing_static_data.mdx @@ -0,0 +1,156 @@ +--- +id: providing_static_data +title: Providing static data +slug: providing-static-data +--- + +Provide static localization data in production mode or if you want to use it [without Tolgee platform](./usage-without-platform). + +## Load translations directly from Tolgee + +The easiest way to get to your localization files on production is to set up [Tolgee Content Delivery](/platform/projects_and_organizations/content_delivery) and fetch the translations directly from there. + +```ts +import { BackendFetch } from '@tolgee/react'; + +const tolgee = Tolgee() + ... + .use(BackendFetch({ prefix: ''})) + ... +``` + +## Use localization files directly + +Other option is to export localization files Tolgee platform manually and bundle them directly with your application. + +### Export with CLI + + +Install [`@tolgee/cli`](/tolgee-cli) package: + +```bash +# install Tolgee cli +npm i -g @tolgee/cli + +# login to tolgee platform +tolgee login + +# pull translation files to ./i18n folder +tolgee pull ./i18n +``` + +### Providing data using dynamic import + +To provide your localization data using dynamic import you will need to add providers for every supported language to [`staticData`](./api/core_package/options#staticdata). + +```jsx +const tolgee = Tolgee().init({ + ... + staticData: { + en: () => import('./i18n/en.json'), + de: () => import('./i18n/de.json'), + + // or for namespaces + 'de:common': () => import('./i18n/common/de.json'), + } +}) +``` + +:::info +For some build systems you'll need to use
+`en: () => import('./i18n/en.json').then(m => m.default)` +::: + + +Using this approach data will be fetched just when it's needed, so you will save some network traffic. + +### Using imported object + +```jsx +import localeEn from './i18n/en.json'; +import localeDe from './i18n/de.json'; + +import commonDe from './i18n/common/de.json' + +... + +const tolgee = Tolgee().init({ + ... + staticData: { + en: localeEn, + de: localeDe, + + // or for namespaces + 'de:common': commonDe, + } +}) +``` + +Using this approach, all localization data are bundled with your application. Don't use it, if our translations files are large. This approach is useful on SSR, when you need the translations to be available for initial render. + +### Providing data using backend plugin + +```jsx +import { BackendFetch } from '@tolgee/react'; + +tolgee.use(BackendFetch()); +``` + +[`BackendFetch`](./api/web_package/plugins#backendfetch) will fetch translation files (en.json, de.json) from `https:///i18n`, so you don't have to list all the files explicitly. + +If you don't use namespaces place your translation into `/i18n` folder: + +``` +├── i18n +│   ├── cs.json +│   └── en.json +├── index.html +``` + +If you use namespaces, it should look like this: + +``` +├── i18n +│   └── common +│       ├── en.json +│       └── de.json +├── index.html +``` + +> This is default behavior, it's possible to customize in [`BackendFetch`](./api/web_package/plugins#backendfetch) plugin. + + +## Data fallbacks + +You can provide local data as a fallback for CDN, for cases when network request fails. + +```ts +const tolgee = Tolgee() + .use(BackendFetch({ + prefix: '', + fallbackOnFail: true + })) + .init({ + staticData: { + en: () => import('./i18n/en.json'), + de: () => import('./i18n/de.json'), + } + ... + }) +``` + +`fallbackOnFail` option will tell Tolgee to look for data in the next source, so it will fetch your local data. **Order of plugins is important here, they are used in the same order as they were added.** + +:::info Plugins are used in the same order as they were added. +Tolgee first checks if data aren't already in cache (data passed directly to `staticData`) then all the plugins and then dynamic data from `staticData` field. +::: + +You can also set a timeout, to only wait certain time for the data from CDN. + +```ts +BackendFetch({ + prefix: '', + fallbackOnFail: true + timeout: 10_000 // 10 seconds +}) +``` \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/shared/ExampleBanner.tsx b/js-sdk_versioned_docs/version-5.x.x/shared/ExampleBanner.tsx new file mode 100644 index 000000000..42789182b --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/shared/ExampleBanner.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import Admonition from '@theme/Admonition'; + +export default function ExampleBanner({ framework, appName }) { + return ( + + You can also check the{' '} + + {framework} example application + {' '} + to learn more. + + ); +} diff --git a/js-sdk_versioned_docs/version-5.x.x/shared/_NamespacesHint.mdx b/js-sdk_versioned_docs/version-5.x.x/shared/_NamespacesHint.mdx new file mode 100644 index 000000000..218fa2d61 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/shared/_NamespacesHint.mdx @@ -0,0 +1,3 @@ +This example will load `common` namespace which can be then used with `t` function. + +> Read more about namespaces [here](../namespaces.mdx). \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/shared/_PreparingForProduction.mdx b/js-sdk_versioned_docs/version-5.x.x/shared/_PreparingForProduction.mdx new file mode 100644 index 000000000..4413958fe --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/shared/_PreparingForProduction.mdx @@ -0,0 +1,8 @@ +Tolgee will automatically omit [`DevTools`](../api/web_package/plugins.mdx#devtools) from your bundle when you build your app for production. So it won't fetch translations directly from Tolgee Platform and it won't allow users to modify translations. + +There are generally two ways how to use translations in production: + + - [Tolgee Content Delivery](/platform/projects_and_organizations/content_delivery) - translations are loaded dynamically from fast and reliable storage + - [Providing localization files directly](/providing_static_data.mdx#use-localization-files-directly) - your translations are bundled with your code + +**In production mode, you should never use localization data directly from Tolgee REST API**, because it can negatively affect your page performance. diff --git a/js-sdk_versioned_docs/version-5.x.x/shared/_TFunctionHint.mdx b/js-sdk_versioned_docs/version-5.x.x/shared/_TFunctionHint.mdx new file mode 100644 index 000000000..c7ce6eeaf --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/shared/_TFunctionHint.mdx @@ -0,0 +1,36 @@ +## `t` function + +This function allows you to render the actual translation. + +#### Simple usage + +```ts +t('app_title') +``` + +The `t` function looks into your translations finds an appropriate key and renders the translation for currently selected language + +> If you use Typescript [read about typed keys](../typed_keys.mdx) + +#### Default value + +You can pass the default value as second parameter, which will be rendered if the key doesn't exist yet. + +```ts +t('non_existant_key', 'Default value') +``` + +#### Parameter interpolation + +You can supply a variable parameters which will then be rendered in your translations. + +```ts +t('user_points', 'User has {count} points', { count: 10 }) + +// -> User has 10 points +``` + +There are more options you can do with the variable parameters, but it depends on selected [formatter plugin](../formatting.mdx). + +> To get the full `t` function specification [check it's API](../api/core_package/tolgee.mdx#t) + diff --git a/js-sdk_versioned_docs/version-5.x.x/text_observer.mdx b/js-sdk_versioned_docs/version-5.x.x/text_observer.mdx new file mode 100644 index 000000000..c88d48c84 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/text_observer.mdx @@ -0,0 +1,57 @@ +--- +id: text_observer +title: Text observer +slug: text-observer +description: "Text observer an alternative observer to default InvisibleObserver: it uses text format instead of invisible characters." + +--- + +Text observer an alternative observer to the default invisible observer. It uses text format instead of invisible characters. Main difference is that initially we put a placeholder into the dom, which is replaced by correct translation by mutation observer. + +Advantage of this is that all necessary data are included in the placeholder itself, so you can e.g. render the translation on the server and if you use Tolgee on FE it will get picked up and can be edited. Text observer is able to update translation live on the page automatically, so in-context will work properly. + +Disadvantages of this approach are that translations will be invalid until `MutationObserver` finds them. So when you use this e.g. in page title it won't work. Also it can cause problems with things like tooltips, because their position is usually based on initial text size. You can generally use `noWrap` parameter where it causes problems: + +```ts +t('title', { noWrap: true }); +``` + +## Usage + +You can switch to text observer through [tolgee options](./api/core_package/options#observertype), where you can also define other options. + +```ts +const tolgee = Tolgee() + .use(DevTools()) + .init({ + ... + observerType: 'text' + observerOptions: { ... } + }); +``` + +## Format + +Placeholders are in following format: + +``` +[|][,][::,...] +``` + +Prefix and suffix can be set through `inputPrefix` and `inputSuffix` options, by default it's `%-%tolgee:` and `%-%`. + +Example for `t('key', 'Default value', { ns: 'namespace', parameter: 'Test' })` will generate this placeholder: + +``` +%-%tolgee:key|namespace,Default value:parameter:Test%-% +``` + +If necessary, special characters are escaped with `\`. + +## When to use + +This wrapper is more universal, as it is able to register translations generated on server easily, however it is more invasive and translation parameters need to be serializable (so it won't work with tag interpolation in React). + +:::info +Text observer is part of [`ObserverPlugin`](./api/web_package/plugins#observerplugin), which is included in [`DevTools`](./api/web_package/plugins#observerplugin). +::: \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/typed_keys.mdx b/js-sdk_versioned_docs/version-5.x.x/typed_keys.mdx new file mode 100644 index 000000000..e5a594f4a --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/typed_keys.mdx @@ -0,0 +1,40 @@ +--- +id: typed_keys +title: Typed translation keys +description: 'Adding type definitions to the translation keys.' +--- + +If you want your keys to be typed with TypeScript you can do so with the following `tolgee.d.ts` file: + +```ts +// tolgee.d.ts + +import type en from './i18n/en.json'; + +declare module '@tolgee/core/lib/types' { + type TranslationsType = typeof en; + + // ensures that nested keys are accessible with "." + type DotNotationEntries = T extends object + ? { + [K in keyof T]: `${K & string}${T[K] extends undefined + ? '' + : T[K] extends object + ? `.${DotNotationEntries}` + : ''}`; + }[keyof T] + : ''; + + // enables both intellisense and new keys without an error + type LiteralUnion = + | LiteralType + | (BaseType & { _?: never }); + + export type TranslationKey = LiteralUnion< + DotNotationEntries, + string + >; +} +``` + +This setup will give you TypeScript intellisense for existing keys but also allow you to add non-existing keys. If you intentionally want TypeScript errors for non-existing keys, just remove `LiteralUnion`. diff --git a/js-sdk_versioned_docs/version-5.x.x/usage_without_platform.mdx b/js-sdk_versioned_docs/version-5.x.x/usage_without_platform.mdx new file mode 100644 index 000000000..6072fc60f --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/usage_without_platform.mdx @@ -0,0 +1,44 @@ +--- +id: usage_without_platform +slug: usage-without-platform +title: Usage without platform +description: 'Use Tolgee SDK as regular i18n library without Tolgee Platform.' +--- + +Tolgee SDK is designed as regular i18n library and therefore it can be used as such without integration with Tolgee Platform. If you won't include `apiUrl` and `apiKey` to the Tolgee options it will only fetch data locally. + +```ts +import { Tolgee, DevTools } from '@tolgee/web'; + +const tolgee = Tolgee() + .use(DevTools()) + .use(FormatSimple()) + .init({ + language: 'en', + staticData: { + ... + } + }); +``` + +> For more options check [Providing static data](./providing-static-data) + +### Static files structure + +You can manage static files manually and use DevTools for easier access to translation key. Tolgee accepts json files with either flat or nested structure: + +```json +{ + "nested.key": "Key" +} +``` + +This is also accepted: + +```json +{ + "nested": { + "key": "Key" + } +} +``` \ No newline at end of file diff --git a/js-sdk_versioned_docs/version-5.x.x/wrapping.mdx b/js-sdk_versioned_docs/version-5.x.x/wrapping.mdx new file mode 100644 index 000000000..cf5c3ec18 --- /dev/null +++ b/js-sdk_versioned_docs/version-5.x.x/wrapping.mdx @@ -0,0 +1,57 @@ +--- +id: wrapping +title: Wrapping and observing +description: 'Learn about two ways of wrapping in Tolgee: text wrapping and invisible wrapping. You can configure this through wrapperMode.' +--- + +## Invisible wrapping (default) + +Tolgee offers unique technology for in-context translating, it combines simplicity of regular i18n libraries with +comfort of in-context translating directly in your app. + +How it works? Try to run tolgee in development mode: + +```ts +import { Tolgee, DevTools } from '@tolgee/web'; + +const tolgee = Tolgee().use(DevTools()).init({ + language: 'en', + apiUrl: 'https://app.tolgee.io', + apiKey: '', +}); + +tolgee.run(); + +console.log(Array.from(tolgee.t('test'))); +``` + +You should see translation individual characters that were produced by [`t`](./api/core_package/tolgee.mdx#t) function. + +```ts +// [... translation value ... , "", "", "", "", "", "", "", "", ""] +``` + +What are these empty characters at the end? They are not actually empty, only zero width, so they are not visible. + +Tolgee passes secret information into each translation, which is then detectable in the dom. +We use [`MutationObserver`](https://developer.mozilla.org/en-US/docs/sdk/API/MutationObserver) +to check all dom changes and search for these secret characters, to find the exact locations +of translations. This method is very reliable and brings basically no overhead on the developer side. You can read more in this [blog post](/blog/2021/12/17/invisible-characters-for-better-localization). + +We call this mechanism wrapping. You can check the information, which is included with the translation by +calling [`tolgee.unwrap`](./api/core_package/tolgee.mdx#unwrap) method, which will give you the information about the translation. + +## Why this solution? + +We wanted something which is almost invisible to the developer, but reliable and doesn't break your app. + +Because we actually return the translation that should be rendered only with some extra characters, you can +use any custom formatter (even outside Tolgee) and modify the string. The secret information will stay intact as it's a valid part of the string. + +## Text wrapping + +There is also a more straightforward solution which is called [text observer](./text-observer), which uses plain text for encoding the key info and can be more practical in some cases. + +:::info +Wrapping is part of [`ObserverPlugin`](./api/web_package/plugins#observerplugin), which is included in [`DevTools`](./api/web_package/plugins#devtools). +::: diff --git a/js-sdk_versioned_sidebars/version-5.x.x-sidebars.json b/js-sdk_versioned_sidebars/version-5.x.x-sidebars.json new file mode 100644 index 000000000..5cbdf5b5d --- /dev/null +++ b/js-sdk_versioned_sidebars/version-5.x.x-sidebars.json @@ -0,0 +1,155 @@ +{ + "someSidebar": [ + "about", + "get_started", + { + "label": "Integrations", + "collapsed": false, + "type": "category", + "items": [ + { + "label": "React", + "type": "category", + "items": [ + "integrations/react/overview", + "integrations/react/installation", + "integrations/react/translating", + "integrations/react/tags_interpolation", + "integrations/react/switching_language", + { + "label": "Next.js", + "type": "category", + "items": [ + "integrations/react/next/introduction", + "integrations/react/next/pages-router", + "integrations/react/next/app-router", + "integrations/react/next/app-router-next-intl" + ] + }, + "integrations/react/ssr", + "integrations/react/react_native", + "integrations/react/api" + ] + }, + { + "label": "Angular", + "type": "category", + "items": [ + "integrations/angular/overview", + "integrations/angular/installation", + "integrations/angular/translating", + "integrations/angular/namespaces", + "integrations/angular/switching_language", + "integrations/angular/html_tags", + "integrations/angular/api" + ] + }, + { + "label": "Vue", + "type": "category", + "items": [ + "integrations/vue/overview", + "integrations/vue/installation", + "integrations/vue/translating", + "integrations/vue/component_interpolation", + "integrations/vue/switching_language", + "integrations/vue/ssr", + "integrations/vue/api" + ] + }, + { + "label": "Svelte", + "type": "category", + "items": [ + "integrations/svelte/overview", + "integrations/svelte/installation", + "integrations/svelte/translating", + "integrations/svelte/switching_language", + "integrations/svelte/api" + ] + }, + { + "label": "i18next", + "type": "category", + "items": [ + "integrations/i18next/installation", + "integrations/i18next/react", + "integrations/i18next/vue", + "integrations/i18next/api" + ] + }, + { + "label": "Vanilla JS", + "type": "category", + "items": [ + "integrations/vanilla/installation", + "integrations/vanilla/initialization", + "integrations/vanilla/translating" + ] + } + ] + }, + { + "label": "Core concepts", + "type": "category", + "collapsed": false, + "items": [ + "formatting", + "language", + "namespaces", + "in_context", + "wrapping", + "providing_static_data", + "filter_by_tags" + ] + }, + "keys_tagging", + "plugins", + "text_observer", + "typed_keys", + "usage_without_platform", + { + "label": "API", + "type": "category", + "items": [ + "api/packages", + { + "label": "Core package", + "type": "category", + "items": [ + "api/core_package/about", + "api/core_package/tolgee", + "api/core_package/options", + "api/core_package/format_simple", + "api/core_package/other_tools", + "api/core_package/events", + "api/core_package/plugin", + "api/core_package/observer_options" + ] + }, + { + "label": "Web package", + "type": "category", + "items": [ + "api/web_package/about", + "api/web_package/plugins", + "api/web_package/tools", + "api/web_package/constants" + ] + } + ] + }, + { + "type": "category", + "label": "Migration to v5", + "items": [ + "migration_to_v5/core", + "migration_to_v5/react", + "migration_to_v5/ngx", + "migration_to_v5/svelte", + "migration_to_v5/vue", + "migration_to_v5/i18next" + ] + } + ] +} diff --git a/js-sdk_versions.json b/js-sdk_versions.json index 4e4fc0336..387979f19 100644 --- a/js-sdk_versions.json +++ b/js-sdk_versions.json @@ -1,3 +1,4 @@ [ + "5.x.x", "4.x.x" ] diff --git a/sidebarJsSdk.js b/sidebarJsSdk.js index 3814eda9d..43e9c5a8c 100644 --- a/sidebarJsSdk.js +++ b/sidebarJsSdk.js @@ -139,17 +139,6 @@ module.exports = { }, ], }, - { - type: 'category', - label: 'Migration to v5', - items: [ - 'migration_to_v5/core', - 'migration_to_v5/react', - 'migration_to_v5/ngx', - 'migration_to_v5/svelte', - 'migration_to_v5/vue', - 'migration_to_v5/i18next', - ], - }, + 'migration-to-v6' ], }; From 39b225c65dec46b2bf8ec667073e2755fec289c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 11:45:32 +0100 Subject: [PATCH 02/17] feat: docs for loadMatrix and loadRequired + availableNs option --- js-sdk/api/core_package/options.mdx | 12 +++++- js-sdk/api/core_package/tolgee.mdx | 62 ++++++++++++++++++++++++++--- js-sdk/migration_to_v6.mdx | 37 +++++++++++++++++ js-sdk/what_is_new_in_v6.mdx | 23 +++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 js-sdk/what_is_new_in_v6.mdx diff --git a/js-sdk/api/core_package/options.mdx b/js-sdk/api/core_package/options.mdx index cfb4c997d..5556e1547 100644 --- a/js-sdk/api/core_package/options.mdx +++ b/js-sdk/api/core_package/options.mdx @@ -68,8 +68,8 @@ projectId: number | string; ### availableLanguages -Languages which can be used for language detection -and also limits which values can be stored. Is derrived from `staticData` keys if not provided. +Specify all available languages. Required for language detection or loading all languages at once ([loadMatrix](./tolgee.mdx#loadmatrix)). +It also limits which values can be stored. Is derrived from `staticData` keys if not provided. ```ts availableLanguages: string[]; @@ -99,6 +99,14 @@ Default namespace when no namespace defined (default: empty string) defaultNs: string; ``` +### availableNs + +Specify all available namespaces. Required for loading all namespaces at once ([loadMatrix](./tolgee.mdx#loadmatrix)). + +```ts +availableNs: string[]; +``` + ### staticData These data go directly to cache or you can specify async diff --git a/js-sdk/api/core_package/tolgee.mdx b/js-sdk/api/core_package/tolgee.mdx index eb8e52b91..d4bea9616 100644 --- a/js-sdk/api/core_package/tolgee.mdx +++ b/js-sdk/api/core_package/tolgee.mdx @@ -274,15 +274,16 @@ when you need to find out if tolgee has all the necessary data before `run` func tolgee.isLoaded(ns?: FallbackNsTranslation): boolean ``` -### getRequiredRecords +### getRequiredDescriptors -Returns records needed for instance to be `loaded` +Returns descriptors of records needed for instance to be `loaded` ```ts -tolgee.getRequiredRecords(): CacheDescriptor[] +tolgee.getRequiredDescriptors(lang?: string, ns?: NsFallback): CacheDescriptorInternal[] ``` -- `ns`: `string` | `string[]` - optional list of namespaces that you are interested in +- `lang`: `string` - optional language (if not present, takes current language) +- `ns`: `string` | `string[]` - optional list of namespaces that you are interested in (if not present takes current active namespaces) ### isDev @@ -335,12 +336,61 @@ Returns wrapped translation info. tolgee.unwrap(text: string): Unwrapped ``` + +### loadRequired + +Load records required by current settings (influenced by current language and active namespaces). + +```ts +loadRequired(options?: LoadRequiredOptions): Promise +``` + +In most cases can be called without any options and will return records which would be loaded +by `run` method initially. + +Options: + - `language`: `string` - optionally override instance language (can be used when you don't know the language at the initialization time) + - `noDev`: `boolean` - load production translations only - skips DevBackend (false by default) + - `useCache`: `boolean` - skip fetching already loaded records (false by default) + +### loadMatrix + +Loads all combinations of specified languages and namespaces. + +```ts +loadRequired(options?: LoadRequiredOptions): Promise +``` + +Example usage: +```ts +const result = await tolgee.loadMatrix({ + languages: ['en', 'cs'], + namespaces: [''] +}) +// loads `en` and `cs` languages for empty namespace + +const result = await tolgee.loadMatrix({ + languages: ['en', 'cs'], + namespaces: ['common', 'info'] +}) +// loads `en:common`, `en:info`, `cs:common` and `cs:info` records +``` + +Options: + - `languages`: `string[] | 'all'` - languages that should be loaded + - use `all` value to load all [`availableLanguages`](./options.mdx#availablelanguages) + - `namespaces`: `string[] | 'all'` - namespaces that should be loaded + - use `all` value to load all [`availableNs`](./options.mdx#availablens) + - `noDev`: `boolean` - load production translations only - skips DevBackend (false by default) + - `useCache`: `boolean` - skip fetching already loaded records (false by default) + + ### loadRecord Manually load record from `Backend` (or `DevBackend` when in dev mode). ```ts -tolgee.loadRecord(descriptors: CacheDescriptor): Promise +tolgee.loadRecord(descriptors: CacheDescriptor, options?: LoadOptions): Promise ``` ### loadRecords @@ -348,7 +398,7 @@ tolgee.loadRecord(descriptors: CacheDescriptor): Promise Manually load multiple records from `Backend` (or `DevBackend` when in dev mode). ```ts -tolgee.loadRecords(descriptors: CacheDescriptor[]): Promise +tolgee.loadRecords(descriptors: CacheDescriptor[], options?: LoadOptions): Promise ``` It loads data together and adds them to cache in one operation, to prevent partly loaded state. diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index c6a9c2927..0d1ee0a15 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -3,4 +3,41 @@ id: migration-to-v6 title: Migration to v6 --- +## Updates to cache + +Cache no longer returns records as maps. +All methods interacting with cache are now returning plain objects instead of `Map`. + +```ts +tolgee.loadRecords(): CacheInternalRecord[] +tolgee.loadRecord(): CacheInternalRecord +tolgee.getRecord(): CacheInternalRecord +tolgee.getAllRecords(): CacheInternalRecord[] +``` + +Returned cache record(s) are now unified like this: +```ts +type CacheInternalRecord = { + data: Record; // replaces Map() + language: string; + namespace: string; + cacheKey: string; +}; +``` + +## Updated `getRequiredRecords` + +This function was renamed to `getRequiredDescriptors`, because it's not returning data from cache, +but description (descriptors) of what need to be in cache. Previous naming might be confusing. + +```ts +tolgee.getRequiredDescriptors(lang?: string, ns?: NsFallback): CacheDescriptorInternal[] +``` + +Previously the method returned the descriptors that are missing in the cache, but now we are returning +all records that are required to be in the cache, so it works the same regardless what is currently in cache. + +Previous behavior can be achieved by comparing the cache content with the results of this function. + +## diff --git a/js-sdk/what_is_new_in_v6.mdx b/js-sdk/what_is_new_in_v6.mdx new file mode 100644 index 000000000..bb5cab842 --- /dev/null +++ b/js-sdk/what_is_new_in_v6.mdx @@ -0,0 +1,23 @@ +--- +id: what-is-new-in-v6 +title: What is new in v6 +--- + +Version 6 is only mild change in comparison to version 5. + +## Data fetching on Backend + +Tolgee SDK can now easily be used to fetch the data on the backend instead of manual +loading of static files. We have two new methods: + +``` +async tolgee.loadRequired() +async tolgee.loadMatrix({ languages: string[], namespaces: string[] }) +``` + +Tolgee can basically be used as a simple client for fetching localization files. +The result of these methods is serializable as a JSON and can be passed to `staticData` property, +so you can easily pass static data from backend to client while re-using the same configuration. + +### + From 5833023a0049a276f1539e9759740e37e06912f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 12:43:14 +0100 Subject: [PATCH 03/17] feat: docs for loadMatrix and loadRequired + availableNs option --- js-sdk/api/core_package/tolgee.mdx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/js-sdk/api/core_package/tolgee.mdx b/js-sdk/api/core_package/tolgee.mdx index d4bea9616..e3fb7e635 100644 --- a/js-sdk/api/core_package/tolgee.mdx +++ b/js-sdk/api/core_package/tolgee.mdx @@ -385,23 +385,31 @@ Options: - `useCache`: `boolean` - skip fetching already loaded records (false by default) -### loadRecord +### loadRecords -Manually load record from `Backend` (or `DevBackend` when in dev mode). +Load records by specifying it's descriptors (language and namespace). ```ts -tolgee.loadRecord(descriptors: CacheDescriptor, options?: LoadOptions): Promise +tolgee.loadRecords(descriptors: CacheDescriptor[], options?: LoadOptions): Promise ``` -### loadRecords +It loads data together and adds them to cache in one operation, to prevent partly loaded state. + +Options: + - `noDev`: `boolean` - load production translations only - skips DevBackend (false by default) + - `useCache`: `boolean` - skip fetching already loaded records (false by default) + +### loadRecord -Manually load multiple records from `Backend` (or `DevBackend` when in dev mode). +Load record by specifying it's descriptor (language and namespace). ```ts -tolgee.loadRecords(descriptors: CacheDescriptor[], options?: LoadOptions): Promise +tolgee.loadRecord(descriptor: CacheDescriptor, options?: LoadOptions): Promise ``` -It loads data together and adds them to cache in one operation, to prevent partly loaded state. +Options: + - `noDev`: `boolean` - load production translations only - skips DevBackend (false by default) + - `useCache`: `boolean` - skip fetching already loaded records (false by default) ### getRecord From 0923a87b07f19393440c89501cea22c5c78a762f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 15:37:24 +0100 Subject: [PATCH 04/17] feat: update next.js setup --- js-sdk/api/core_package/events.mdx | 29 ++++--- js-sdk/api/web_package/tools.mdx | 4 - js-sdk/fetching_translations.mdx | 67 +++++++++++++++ js-sdk/integrations/react/next/app_router.mdx | 74 ++++++----------- .../react/next/app_router_next_intl.mdx | 82 ++++++------------- .../integrations/react/next/pages_router.mdx | 34 +++----- js-sdk/integrations/vue/api.mdx | 4 - .../vue/component_interpolation.mdx | 4 - js-sdk/migration_to_v6.mdx | 38 ++++++++- js-sdk/what_is_new_in_v6.mdx | 23 ------ sidebarJsSdk.js | 1 + 11 files changed, 183 insertions(+), 177 deletions(-) create mode 100644 js-sdk/fetching_translations.mdx delete mode 100644 js-sdk/what_is_new_in_v6.mdx diff --git a/js-sdk/api/core_package/events.mdx b/js-sdk/api/core_package/events.mdx index 2d5c5516b..85052135b 100644 --- a/js-sdk/api/core_package/events.mdx +++ b/js-sdk/api/core_package/events.mdx @@ -11,7 +11,7 @@ Tolgee events which can be listened through `tolgee.on` method. Emitted on language change. ```ts -tolgee.on('language', handler: ListenerHandler) +tolgee.on('language', handler: Handler) ``` ### pendingLanguage @@ -19,7 +19,7 @@ tolgee.on('language', handler: ListenerHandler) Emitted on language change. Before languages are loaded (when tolgee is running). ```ts -tolgee.on('pendingLanguage', handler: ListenerHandler) +tolgee.on('pendingLanguage', handler: Handler) ``` ### loading @@ -27,7 +27,7 @@ tolgee.on('pendingLanguage', handler: ListenerHandler) Emitted on loading change. Changes when tolgee is loading some data for the first time. ```ts -tolgee.on('loading', handler: ListenerHandler) +tolgee.on('loading', handler: Handler) ``` ### fetching @@ -35,7 +35,7 @@ tolgee.on('loading', handler: ListenerHandler) Emitted on fetching change. Changes when tolgee is fetching any data. ```ts -tolgee.on('fetching', handler: ListenerHandler) +tolgee.on('fetching', handler: Handler) ``` ### initialLoad @@ -43,7 +43,7 @@ tolgee.on('fetching', handler: ListenerHandler) Emitted when `run` method finishes. ```ts -tolgee.on('initialLoad', handler: ListenerHandler) +tolgee.on('initialLoad', handler: Handler) ``` ### running @@ -51,7 +51,7 @@ tolgee.on('initialLoad', handler: ListenerHandler) Emitted when internal `running` state changes. ```ts -tolgee.on('initialLoad', handler: ListenerHandler) +tolgee.on('running', handler: Handler) ``` ### cache @@ -59,16 +59,23 @@ tolgee.on('initialLoad', handler: ListenerHandler) Emitted when cache changes. ```ts -tolgee.on('cache', handler: ListenerHandler) +tolgee.on('cache', handler: Handler) ``` ### update -Emitted when any key needs (or might need) to be re-rendered. Similar to `tolgee.onNsUpdate`, -except we intercept all events, not just selection. +Emitted when any key needs (or might need) to be re-rendered. It's derrived from other events +(`initialLoad`, `language`, `cache`), the events are grouped when multiple happen at the same time, +so it only results to one `update` event being emitted. ```ts -tolgee.on('update', handler: ListenerHandler) +tolgee.on('update', handler: CombinedHandler) +``` + +You can check what events triggered this change event from the first argument of the handler. + +```ts +tolgee.on('update', (events) => events.forEach(e => ...)) ``` ### error @@ -79,6 +86,6 @@ Emitted when there is an error. You can intercept different types of errors, con - `LanguageStorageError` - error when loading/saving language through language storage plugin ```ts -tolgee.on('error', handler: ListenerHandler) +tolgee.on('error', handler: Handler) ``` diff --git a/js-sdk/api/web_package/tools.mdx b/js-sdk/api/web_package/tools.mdx index 820ca9286..e99d09e0e 100644 --- a/js-sdk/api/web_package/tools.mdx +++ b/js-sdk/api/web_package/tools.mdx @@ -8,10 +8,6 @@ slug: tools As `DevTools` are automatically omitted on production, `@tolgee/web/tools` can be used to include it unconditionally. -:::info -Tools were moved from `@tolgee/web` in version 5.2.0. -::: - ## `InContextTools` diff --git a/js-sdk/fetching_translations.mdx b/js-sdk/fetching_translations.mdx new file mode 100644 index 000000000..3c506ae3b --- /dev/null +++ b/js-sdk/fetching_translations.mdx @@ -0,0 +1,67 @@ +--- +id: fetching_translations +title: Fetching translations +--- + +:::info +New in SDK version 6 +::: + +Tolgee SDK can be used to simply load correct translation files from tolgee platform or from static data. +This is primarly useful on the server, when you need to prefetch data and pass them to frontend as a JSON. + +These methods also load the data to cache, so you can then call `t` function to render the translations. + +## loadRequired + +This method fetches the same data as `tolgee.run()` method - based on current options of `language`, `ns` (and their fallbacks). + +```ts +// on the server +const translations = JSON.stringify(await tolgee.loadRequired()) + +// ... pass translations to the client + +// on the client +tolgee.addStaticData(translations) +// now the client doesn't have to fetch anything, when `run` is called +``` + +> Check the [method API](./api/core_package/tolgee.mdx#loadrequired) + +## loadMatrix + +Use this method for easy prefetching of user defined records. + +```ts +const translations = await tolgee.loadMatrix({ + languages: ['en', 'cs'] + namespaces: ['common', 'info'] +}) +// loads `en:common`, `en:info`, `cs:common` and `cs:info` records +``` + +Let's say we want to load all namespaces on the server so we can render them without loading later. + +```ts +await tolgee.loadMatrix({ + languages: ['en'] + namespaces: 'all' +}) + +// now all namespaces are now prefetched for english + +t('app_title', { ns: 'info' }) +``` + +For this to work, you need to specify [`availableNs`](./api/core_package/options.mdx#availablens) or [`availableLanguages`](./api/core_package/options.mdx#availablelanguages). + +> Check the [method API](./api/core_package/tolgee.mdx#loadmatrix) + +# Data caching on the server + +By default these methods always re-fetch the data when called (requests are only de-duped). + +If you want to re-use already fetched data from cache, you can pass option `{ useCache: true }`. +However on the server this is not recommended, since if the tolgee instance would live, the data would't be renewed. +Instead we recommend external caching solution (for example next.js caching). \ No newline at end of file diff --git a/js-sdk/integrations/react/next/app_router.mdx b/js-sdk/integrations/react/next/app_router.mdx index f1cf6fa08..33823d91a 100644 --- a/js-sdk/integrations/react/next/app_router.mdx +++ b/js-sdk/integrations/react/next/app_router.mdx @@ -31,10 +31,6 @@ To implement localization to your app, you need to install the `@tolgee/react` p npm install @tolgee/react ``` -:::info -Use `@tolgee/react` version `5.30.0` and higher -::: - ## Folder structure The folder structure of your project should resemble the following: @@ -90,25 +86,6 @@ export const ALL_LANGUAGES = ['en', 'cs']; export const DEFAULT_LANGUAGE = 'en'; -export async function getStaticData( - languages: string[], - namespaces: string[] = [''] -) { - const result: Record = {}; - for (const lang of languages) { - for (const namespace of namespaces) { - if (namespace) { - result[`${lang}:${namespace}`] = ( - await import(`../../messages/${namespace}/${lang}.json`) - ).default; - } else { - result[lang] = (await import(`../../messages/${lang}.json`)).default; - } - } - } - return result; -} - export function TolgeeBase() { return Tolgee() .use(FormatSimple()) @@ -117,13 +94,17 @@ export function TolgeeBase() { .updateDefaults({ apiKey, apiUrl, + staticData: { + en: () => import('../../messages/en.json'), + cs: () => import('../../messages/cs.json'), + } }); } ``` ### Client configutation -The client configuration is very similar to the [Pages Router set up](./pages-router). It serves the purpose of translating client components and also enables the in-context functionality for server-rendered components. +The client configuration is very similar to the [Pages Router setup](./pages-router). It serves the purpose of translating client components and also enables the in-context functionality for server-rendered components. ```tsx title="src/tolgee/client.tsx" 'use client'; @@ -149,6 +130,7 @@ export const TolgeeNextProvider = ({ const router = useRouter(); useEffect(() => { + // this ensures server components refresh, after translation change const { unsubscribe } = tolgee.on('permanentChange', () => { router.refresh(); }); @@ -158,7 +140,6 @@ export const TolgeeNextProvider = ({ return ( @@ -209,25 +190,24 @@ Your app will utilize the React server cache for sharing Tolgee instance across One important difference from the client setup is the utilization of [`fullKeyEncode`](../../../api/core_package/observer-options#fullkeyencode). `fullKeyEncode` ensures that translations rendered on the server are correctly picked up and interactive for in-context mode. ```tsx title="src/tolgee/server.tsx" -import { TolgeeBase, ALL_LANGUAGES, getStaticData } from './shared'; -import { createServerInstance } from '@tolgee/react/server'; import { getLanguage } from './language'; +import { TolgeeBase } from './shared'; +import { createServerInstance } from '@tolgee/react/server'; -export const { getTolgee, getTranslate, T } = createServerInstance({ - getLocale: getLanguage, - createTolgee: async (locale) => - TolgeeBase().init({ - // including all locales - // on server we are not concerned about bundle size - staticData: await getStaticData(ALL_LANGUAGES), - observerOptions: { - fullKeyEncode: true, - }, - language: locale, - fetch: async (input, init) => - fetch(input, { ...init, next: { revalidate: 0 } }), - }), -}); +export const { getTolgee, getTranslate, T, getTolgeeStaticInstance } = + createServerInstance({ + getLocale: getLanguage, + createTolgee: async (language) => { + const tolgee = TolgeeBase().init({ + observerOptions: { + fullKeyEncode: true, + }, + language, + }); + await tolgee.loadRequired(); + return tolgee; + }, + }); ``` ## Use the `TolgeeNextProvider` @@ -237,20 +217,18 @@ The next step is to wrap the children with the `TolgeeNextProvider`. Update the ```tsx title="src/app/layout.tsx" import { ReactNode } from 'react'; import { TolgeeNextProvider } from '@/tolgee/client'; -import { getStaticData } from '@/tolgee/shared'; +import { getTolgee } from '@/tolgee/server'; import { getLanguage } from '@/tolgee/language'; -import './style.css'; type Props = { children: ReactNode; - params: { locale: string }; }; export default async function LocaleLayout({ children }: Props) { const locale = await getLanguage(); - // it's important you provide all data which are needed for initial render - // so current language and also fallback languages + necessary namespaces - const staticData = await getStaticData([locale]); + const tolgee = await getTolgee(); + // serializable data that are passed to client components + const staticData = await tolgee.loadRequired(); return ( diff --git a/js-sdk/integrations/react/next/app_router_next_intl.mdx b/js-sdk/integrations/react/next/app_router_next_intl.mdx index dcd59d86f..26136e418 100644 --- a/js-sdk/integrations/react/next/app_router_next_intl.mdx +++ b/js-sdk/integrations/react/next/app_router_next_intl.mdx @@ -36,10 +36,6 @@ To implement localization to your app, you need to install the `next-intl` and ` npm install next-intl @tolgee/react ``` -:::info -Use `@tolgee/react` version `5.30.0` and higher -::: - ## Folder structure The folder structure of your project should resemble the following: @@ -99,25 +95,6 @@ export const ALL_LANGUAGES = ['en', 'cs', 'de', 'fr']; export const DEFAULT_LANGUAGE = 'en'; -export async function getStaticData( - languages: string[], - namespaces: string[] = [''] -) { - const result: Record = {}; - for (const lang of languages) { - for (const namespace of namespaces) { - if (namespace) { - result[`${lang}:${namespace}`] = ( - await import(`../../messages/${namespace}/${lang}.json`) - ).default; - } else { - result[lang] = (await import(`../../messages/${lang}.json`)).default; - } - } - } - return result; -} - export function TolgeeBase() { return Tolgee() .use(FormatSimple()) @@ -168,7 +145,6 @@ export const TolgeeNextProvider = ({ return ( @@ -187,25 +163,22 @@ One important difference from the client setup is the utilization of [`fullKeyEn ```tsx title="src/tolgee/server.tsx" import { getLocale } from 'next-intl/server'; - -import { TolgeeBase, ALL_LANGUAGES, getStaticData } from './shared'; import { createServerInstance } from '@tolgee/react/server'; +import { TolgeeBase } from './shared'; export const { getTolgee, getTranslate, T } = createServerInstance({ getLocale: getLocale, - createTolgee: async (language) => - TolgeeBase().init({ - // including all languages - // on server we are not concerned about bundle size - staticData: await getStaticData(ALL_LANGUAGES), + createTolgee: async (language) => { + const tolgee = TolgeeBase().init({ observerOptions: { fullKeyEncode: true, }, language, - // using custom fetch to avoid aggressive caching - fetch: async (input, init) => - fetch(input, { ...init, next: { revalidate: 0 } }), - }), + }); + // preload all the languages for the server instance + await tolgee.loadRequired(); + return tolgee; + }, }); ``` @@ -214,32 +187,29 @@ export const { getTolgee, getTranslate, T } = createServerInstance({ The next step is to wrap the children with the `TolgeeNextProvider`. Update the `layout.tsx` file with the following code: ```tsx title="src/app/[locale]/layout.tsx" +import React, { ReactNode } from 'react'; import { notFound } from 'next/navigation'; -import { ReactNode } from 'react'; import { TolgeeNextProvider } from '@/tolgee/client'; -import { ALL_LANGUAGES, getStaticData } from '@/tolgee/shared'; +import { ALL_LANGUAGES } from '@/tolgee/shared'; +import { getTolgee } from '@/tolgee/server'; type Props = { children: ReactNode; - params: { locale: string }; + params: Promise<{ locale: string }>; }; -export default async function LocaleLayout({ - children, - params: { locale }, -}: Props) { +export default async function LocaleLayout({ children, params }: Props) { + const { locale } = await params; if (!ALL_LANGUAGES.includes(locale)) { notFound(); } - - // it's important you provide all data which are needed for initial render - // so current language and also fallback languages + necessary namespaces - const staticData = await getStaticData([locale, 'en']); + const tolgee = await getTolgee(); + const records = await tolgee.loadRequired(); return ( - + {children} @@ -277,14 +247,14 @@ export const config = { Next, create a `src/navigation.ts` file. This provides easy access to the navigation APIs in your components. ```ts title="src/navigation.ts" -import { createSharedPathnamesNavigation } from 'next-intl/navigation'; +import { createNavigation } from 'next-intl/navigation'; import { ALL_LANGUAGES } from './tolgee/shared'; // read more about next-intl library // https://next-intl-docs.vercel.app -export const { Link, redirect, usePathname, useRouter } = - createSharedPathnamesNavigation({ locales: ALL_LANGUAGES }); -``` +export const { Link, redirect, usePathname, useRouter } = createNavigation({ + locales: ALL_LANGUAGES, +});``` > To gain a comprehensive understanding of how `next-intl` operates, read [their documentation](https://next-intl-docs.vercel.app/docs/getting-started/app-router-server-components). This example utilizes the necessary setup for proper routing. Not all the listed configurations are required. @@ -309,12 +279,12 @@ The `i18n/request.ts` is required by `next-intl` package, we don't actually need ```ts title="src/i18n.ts" import { getRequestConfig } from 'next-intl/server'; -import { getStaticData } from '../tolgee/shared'; -export default getRequestConfig(async ({ locale }) => { +export default getRequestConfig(async ({ requestLocale }) => { + const locale = await requestLocale; return { - // do this to make next-intl not emitting any warnings - messages: { locale }, + // do this to make next-intl not emmit any warnings + messages: { locale: locale! }, }; }); ``` @@ -337,7 +307,7 @@ export const LangSelector: React.FC = () => { const locale = tolgee.getLanguage(); const router = useRouter(); const pathname = usePathname(); - const [isPending, startTransition] = useTransition(); + const [_, startTransition] = useTransition(); function onSelectChange(event: ChangeEvent) { const newLocale = event.target.value; diff --git a/js-sdk/integrations/react/next/pages_router.mdx b/js-sdk/integrations/react/next/pages_router.mdx index 329692cd3..4a4f6e7c1 100644 --- a/js-sdk/integrations/react/next/pages_router.mdx +++ b/js-sdk/integrations/react/next/pages_router.mdx @@ -25,10 +25,6 @@ To implement localization to your app, you need to install the `@tolgee/react` p npm install @tolgee/react ``` -:::info -Use `@tolgee/react` version `5.30.0` and higher -::: - ## Folder structure The folder structure of your project should resemble the following: @@ -82,25 +78,6 @@ This file contains tolgee setup and helper function for loading static files. import { FormatIcu } from '@tolgee/format-icu'; import { Tolgee, DevTools } from '@tolgee/react'; -export async function getStaticData( - languages: string[], - namespaces: string[] = [''] -) { - const result: Record = {}; - for (const lang of languages) { - for (const namespace of namespaces) { - if (namespace) { - result[`${lang}:${namespace}`] = ( - await import(`../messages/${namespace}/${lang}.json`) - ).default; - } else { - result[lang] = (await import(`../messages/${lang}.json`)).default; - } - } - } - return result; -} - export const tolgee = Tolgee() .use(FormatIcu()) .use(DevTools()) @@ -109,6 +86,10 @@ export const tolgee = Tolgee() defaultLanguage: 'en', apiKey: process.env.NEXT_PUBLIC_TOLGEE_API_KEY, apiUrl: process.env.NEXT_PUBLIC_TOLGEE_API_URL, + staticData: { + en: () => import('../messages/en.json'), + cs: () => import('../messages/cs.json'), + } }); ``` @@ -150,7 +131,12 @@ MyApp.getInitialProps = async ( context: AppContext ): Promise => { const ctx = await App.getInitialProps(context); - return { ...ctx, staticData: await getStaticData([context.ctx.locale!]) }; + return { + ...ctx, + staticData: await tolgee.loadRequired({ + language: context.ctx.locale!, + }), + }; }; ``` diff --git a/js-sdk/integrations/vue/api.mdx b/js-sdk/integrations/vue/api.mdx index 3171dc30e..07efa400f 100644 --- a/js-sdk/integrations/vue/api.mdx +++ b/js-sdk/integrations/vue/api.mdx @@ -114,10 +114,6 @@ import { T } from '@tolgee/vue'; ### Children -:::info -Feature available from version 5.24.0 -::: - Pass children to the `T` component that gets mapped to the translation parameters respective to their slot names. Allows implementation of custom components as a part of the translation. ```html diff --git a/js-sdk/integrations/vue/component_interpolation.mdx b/js-sdk/integrations/vue/component_interpolation.mdx index 14572d6a5..07b071172 100644 --- a/js-sdk/integrations/vue/component_interpolation.mdx +++ b/js-sdk/integrations/vue/component_interpolation.mdx @@ -6,10 +6,6 @@ slug: component-interpolation description: "Learn to use component interpolation in Vue with Tolgee. Guide covers implementing custom components within translations, using FormatIcu and the T component. Includes code examples and usage tips." --- -:::info -Feature available from version 5.24.0 -::: - You might want to use a Component for certain part of the translated string. For example, let's take the translated string `Click this link`. You want to add the anchor tag to the word `link`. Using the integration library, you can implement this easily. The integration library, uses [`FormatIcu`](/formatting.mdx#icu-formatter) which supports component interpolation. You can map variables in translations to custom Vue components. diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index 0d1ee0a15..2cc537149 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -9,8 +9,8 @@ Cache no longer returns records as maps. All methods interacting with cache are now returning plain objects instead of `Map`. ```ts -tolgee.loadRecords(): CacheInternalRecord[] -tolgee.loadRecord(): CacheInternalRecord +tolgee.loadRecords(): Promise +tolgee.loadRecord(): Promise tolgee.getRecord(): CacheInternalRecord tolgee.getAllRecords(): CacheInternalRecord[] ``` @@ -39,5 +39,37 @@ all records that are required to be in the cache, so it works the same regardles Previous behavior can be achieved by comparing the cache content with the results of this function. -## +## Removed obscure `onNsUpdate` subscribing +Previously it was possible to subscribe to specific namespace changes like this: + +```ts +const listener = tolgee.onNsUpdate(...) +// subscribes namespace +listener.subscribeNs(ns) +// unsubscribe completely +listener.unsubscribe() +``` + +You can replace that with `update` event. + +```ts +tolgee.on('update', ...) +``` + +If you want to only react to certain namespaces, the event handler recieves an array of events which +led to invocation of `update` event (`update` is derrived from other events). + +```ts +tolgee.on('update', (events) => { + if (events.every(e => (e.type !== 'cache' || e.value.namespace === 'namespace'))) { + handler() + } +}) +``` + +## React + +`TolgeeProvider` component `useSuspense` is now false by default. + +## Vue diff --git a/js-sdk/what_is_new_in_v6.mdx b/js-sdk/what_is_new_in_v6.mdx deleted file mode 100644 index bb5cab842..000000000 --- a/js-sdk/what_is_new_in_v6.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: what-is-new-in-v6 -title: What is new in v6 ---- - -Version 6 is only mild change in comparison to version 5. - -## Data fetching on Backend - -Tolgee SDK can now easily be used to fetch the data on the backend instead of manual -loading of static files. We have two new methods: - -``` -async tolgee.loadRequired() -async tolgee.loadMatrix({ languages: string[], namespaces: string[] }) -``` - -Tolgee can basically be used as a simple client for fetching localization files. -The result of these methods is serializable as a JSON and can be passed to `staticData` property, -so you can easily pass static data from backend to client while re-using the same configuration. - -### - diff --git a/sidebarJsSdk.js b/sidebarJsSdk.js index 43e9c5a8c..95af9b58a 100644 --- a/sidebarJsSdk.js +++ b/sidebarJsSdk.js @@ -101,6 +101,7 @@ module.exports = { 'wrapping', 'providing_static_data', 'filter_by_tags', + 'fetching_translations' ], }, 'keys_tagging', From 987b83366fc96a3c61876cb582fe0830ee395efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 16:09:53 +0100 Subject: [PATCH 05/17] feat: add docs about cdn --- js-sdk/api/core_package/tolgee.mdx | 21 ------------------- js-sdk/integrations/react/next/app_router.mdx | 3 +++ .../react/next/app_router_next_intl.mdx | 3 +++ .../integrations/react/next/pages_router.mdx | 4 ++++ .../react/next/shared/_CdnFetchPlugin.mdx | 17 +++++++++++++++ js-sdk/integrations/vanilla/translating.mdx | 12 +---------- js-sdk/migration_to_v6.mdx | 2 +- sidebarJsSdk.js | 2 +- 8 files changed, 30 insertions(+), 34 deletions(-) create mode 100644 js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx diff --git a/js-sdk/api/core_package/tolgee.mdx b/js-sdk/api/core_package/tolgee.mdx index e3fb7e635..cb78513e2 100644 --- a/js-sdk/api/core_package/tolgee.mdx +++ b/js-sdk/api/core_package/tolgee.mdx @@ -102,27 +102,6 @@ listener.unsubscribe() Check [Events](./events) for details. -### onNsUpdate - -Allows you to listen to key changes of namespaces that you pick. - -```ts -tolgee.onNsUpdate(handler: ListenerHandler): ListenerSelective -``` - -Returns object, which allows you to subscribe to namespaces. - -```ts -const listener = tolgee.onNsUpdate(...) -// subscribes namespace -listener.subscribeNs(ns) -// unsubscribe completely -listener.unsubscribe() -``` - -Use this when you want to make sure, you always update translation when it's necessary. -It internally stores list of namespaces that you are interested in and calls the handler, when necessary. - ### t Returns formatted translation based on current language. If `Observer` is present and tolgee is running, `wraps` the result to be identifiable in the DOM. diff --git a/js-sdk/integrations/react/next/app_router.mdx b/js-sdk/integrations/react/next/app_router.mdx index 33823d91a..1e8a4340a 100644 --- a/js-sdk/integrations/react/next/app_router.mdx +++ b/js-sdk/integrations/react/next/app_router.mdx @@ -8,6 +8,7 @@ description: "Get started with Tolgee's integration for Next.js. Learn how to ad import AppRouterTranslating from './shared/_AppRouterTranslating.mdx' import ExampleBanner from '../../../shared/ExampleBanner'; import LimitationsOfServerComponents from './shared/_LimitationsOfServerComponents.mdx' +import CdnFetchPlugin from './shared/_CdnFetchPlugin.mdx'; @@ -280,4 +281,6 @@ export const LangSelector: React.FC = () => { + + diff --git a/js-sdk/integrations/react/next/app_router_next_intl.mdx b/js-sdk/integrations/react/next/app_router_next_intl.mdx index 26136e418..a6aa018e1 100644 --- a/js-sdk/integrations/react/next/app_router_next_intl.mdx +++ b/js-sdk/integrations/react/next/app_router_next_intl.mdx @@ -8,6 +8,7 @@ description: "Get started with Tolgee's integration for Next.js. Learn how to ad import AppRouterTranslating from './shared/_AppRouterTranslating.mdx' import ExampleBanner from '../../../shared/ExampleBanner'; import LimitationsOfServerComponents from './shared/_LimitationsOfServerComponents.mdx' +import CdnFetchPlugin from './shared/_CdnFetchPlugin.mdx'; This guide shows how to include language information into the page URL, so your application is SEO friendly. @@ -330,4 +331,6 @@ Make sure you use navigation-related components from `@/navigation` instead nati + + diff --git a/js-sdk/integrations/react/next/pages_router.mdx b/js-sdk/integrations/react/next/pages_router.mdx index 4a4f6e7c1..d93c32433 100644 --- a/js-sdk/integrations/react/next/pages_router.mdx +++ b/js-sdk/integrations/react/next/pages_router.mdx @@ -6,6 +6,8 @@ description: "Get started with Tolgee's integration for Next.js. Learn how to ad --- import ExampleBanner from '../../../shared/ExampleBanner'; +import CdnFetchPlugin from './shared/_CdnFetchPlugin.mdx'; + @@ -174,6 +176,8 @@ The next step is to render the translated text for the selected locale. Use the ``` + + That's it! You have successfully implemented translation to your Next.js applicaiton. You can also use translation methods described in the [React Translating documentation](../translating.mdx). diff --git a/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx b/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx new file mode 100644 index 000000000..c23f0d483 --- /dev/null +++ b/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx @@ -0,0 +1,17 @@ +## Usage of Tolgee Content Delivery + +If you want to use Tolgee Content Delivery instead of local static data, +we recommend usage of next.js cache functionality by configuring the BackendFetch plugin in following way: + +```ts +tolgee.use( + BackendFetch({ + prefix: '', + next: { + revalidate: 1000 * 60, // cache CDN data for one minute + }, + }) +) +``` + +You can remove `staticData` or use them as fallback ([read more](/js-sdk/providing-static-data#load-translations-directly-from-tolgee)). \ No newline at end of file diff --git a/js-sdk/integrations/vanilla/translating.mdx b/js-sdk/integrations/vanilla/translating.mdx index 262b56ecb..8a6d13ee4 100644 --- a/js-sdk/integrations/vanilla/translating.mdx +++ b/js-sdk/integrations/vanilla/translating.mdx @@ -24,14 +24,4 @@ You can [listen](../../api/core_package/tolgee.mdx#on) for translation change wi tolgee.on('update', () => { domElement.textContent = tolgee.t('key', 'Default value'); }); -``` - -This event gets triggered when any translation need to be updated, so it might be inefficient if you use many namespaces. Therefore there is also [`onNsUpdate`](../../api/core_package/tolgee.mdx#onnsupdate) method, which allows you to subscribe only to selected namespaces. - -```ts -tolgee - .onNsUpdate(() => { - domElement.textContent = tolgee.t('key', 'Default value'); - }) - .subscribeNs('my_namespace'); -``` +``` \ No newline at end of file diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index 2cc537149..297c74918 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -1,5 +1,5 @@ --- -id: migration-to-v6 +id: migration_to_v6 title: Migration to v6 --- diff --git a/sidebarJsSdk.js b/sidebarJsSdk.js index 95af9b58a..53af34660 100644 --- a/sidebarJsSdk.js +++ b/sidebarJsSdk.js @@ -140,6 +140,6 @@ module.exports = { }, ], }, - 'migration-to-v6' + 'migration_to_v6' ], }; From 01a30632e5c8d408b18a7227a24c8bf1eb7ad1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 16:20:24 +0100 Subject: [PATCH 06/17] feat: vue migration --- js-sdk/integrations/vue/ssr.mdx | 4 ++-- js-sdk/migration_to_v6.mdx | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/js-sdk/integrations/vue/ssr.mdx b/js-sdk/integrations/vue/ssr.mdx index 7d5c811c1..1950e3657 100644 --- a/js-sdk/integrations/vue/ssr.mdx +++ b/js-sdk/integrations/vue/ssr.mdx @@ -43,7 +43,7 @@ const language = ?; ... @@ -65,7 +65,7 @@ const staticData = ?; ... diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index 297c74918..df308db90 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -70,6 +70,29 @@ tolgee.on('update', (events) => { ## React -`TolgeeProvider` component `useSuspense` is now false by default. +`TolgeeProvider` component `useSuspense` is now false by default. It didn't work well with next.js, +so it's off by default. + +Old: +```tsx + +``` + +New: +```tsx + +``` ## Vue + +`TolgeeProvider` ssr interface is now same as the react one. + +Old: +```html + +``` + +New: +```html + +``` From 5b31553ef08e6e5d39a8475612045de4da34284d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 16:39:40 +0100 Subject: [PATCH 07/17] chore: fix next.js cache options --- js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx b/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx index c23f0d483..68d938fcf 100644 --- a/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx +++ b/js-sdk/integrations/react/next/shared/_CdnFetchPlugin.mdx @@ -8,7 +8,7 @@ tolgee.use( BackendFetch({ prefix: '', next: { - revalidate: 1000 * 60, // cache CDN data for one minute + revalidate: 60, // cache CDN data for one minute }, }) ) From 50a5df155320b51a10561f2af62a597db21a6448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 16:51:17 +0100 Subject: [PATCH 08/17] fix: migration introduction --- js-sdk/migration_to_v6.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index df308db90..9a4860486 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -3,6 +3,9 @@ id: migration_to_v6 title: Migration to v6 --- +In v6 we introduced serveral minor breaking changes, which were mostly necessary for better support of +next.js (and server side rendering in general). + ## Updates to cache Cache no longer returns records as maps. @@ -28,7 +31,7 @@ type CacheInternalRecord = { ## Updated `getRequiredRecords` This function was renamed to `getRequiredDescriptors`, because it's not returning data from cache, -but description (descriptors) of what need to be in cache. Previous naming might be confusing. +but description (descriptors) of what need to be in cache. Previous name was confusing. ```ts tolgee.getRequiredDescriptors(lang?: string, ns?: NsFallback): CacheDescriptorInternal[] From 21eae90bf0f06f3354c649a6137a7a06a62cc913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 30 Dec 2024 16:52:07 +0100 Subject: [PATCH 09/17] fix: migration introduction --- js-sdk/migration_to_v6.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index 9a4860486..b7f439459 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -1,9 +1,11 @@ --- id: migration_to_v6 title: Migration to v6 +description: 'In v6 we introduced a few minor breaking changes, which were mostly necessary for better support of +next.js' --- -In v6 we introduced serveral minor breaking changes, which were mostly necessary for better support of +In v6 we introduced a few minor breaking changes, which were mostly necessary for better support of next.js (and server side rendering in general). ## Updates to cache From 716478568e1d1c0aeecddd339d573a456d876eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 13:03:42 +0100 Subject: [PATCH 10/17] fix: grammar --- js-sdk/migration_to_v6.mdx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/js-sdk/migration_to_v6.mdx b/js-sdk/migration_to_v6.mdx index b7f439459..59bf8f1dd 100644 --- a/js-sdk/migration_to_v6.mdx +++ b/js-sdk/migration_to_v6.mdx @@ -6,12 +6,12 @@ next.js' --- In v6 we introduced a few minor breaking changes, which were mostly necessary for better support of -next.js (and server side rendering in general). +next.js (and server-side rendering in general). ## Updates to cache -Cache no longer returns records as maps. -All methods interacting with cache are now returning plain objects instead of `Map`. +The cache no longer returns records as maps. +All methods interacting with the cache are now returning plain objects instead of `Map`. ```ts tolgee.loadRecords(): Promise @@ -32,15 +32,15 @@ type CacheInternalRecord = { ## Updated `getRequiredRecords` -This function was renamed to `getRequiredDescriptors`, because it's not returning data from cache, -but description (descriptors) of what need to be in cache. Previous name was confusing. +This function was renamed to `getRequiredDescriptors`, because it's not returning data from the cache, +but description (descriptors) of what needs to be in cache. The previous name was confusing. ```ts tolgee.getRequiredDescriptors(lang?: string, ns?: NsFallback): CacheDescriptorInternal[] ``` -Previously the method returned the descriptors that are missing in the cache, but now we are returning -all records that are required to be in the cache, so it works the same regardless what is currently in cache. +Previously the method returned the descriptors that were missing in the cache, but now we are returning +all the missing records regardless of what is currently in the cache. Previous behavior can be achieved by comparing the cache content with the results of this function. @@ -62,14 +62,14 @@ You can replace that with `update` event. tolgee.on('update', ...) ``` -If you want to only react to certain namespaces, the event handler recieves an array of events which -led to invocation of `update` event (`update` is derrived from other events). +If you only want to react to certain namespaces, the event handler receives an array of events which +led to the invocation of `update` event (`update` is derived from other events). ```ts tolgee.on('update', (events) => { if (events.every(e => (e.type !== 'cache' || e.value.namespace === 'namespace'))) { handler() - } + } }) ``` @@ -90,7 +90,7 @@ New: ## Vue -`TolgeeProvider` ssr interface is now same as the react one. +`TolgeeProvider` ssr interface is now the same as the react one. Old: ```html From 40dd9f219d09b22e7a06a47c8a1f812fdf2851cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 14:21:59 +0100 Subject: [PATCH 11/17] feat: blog post about V6 --- blog/2025-01-06-sdk-v6.mdx | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 blog/2025-01-06-sdk-v6.mdx diff --git a/blog/2025-01-06-sdk-v6.mdx b/blog/2025-01-06-sdk-v6.mdx new file mode 100644 index 000000000..9d4071e4a --- /dev/null +++ b/blog/2025-01-06-sdk-v6.mdx @@ -0,0 +1,63 @@ +--- +slug: sdk-v6 +title: 'Tolgee SDK Version 6 released! 🎉🎉' +description: "Expand globally with Tolgee’s new professional translation feature—easily order, manage, and collaborate with agencies in one platform!" +image: /img/blog/order-translation/cover-light.webp +authors: [sgranat] +tags: ['tolgee', 'localization', 'translation'] +--- + +In new version of Tolgee SDK, we simplify the Next.js integration, we want to provide a smooth experience, +without complicated setup. And now we are one step closer. + + + +## Using Tolgee as a client for fetching translations + +With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. + +### Fetching Required Translations with `loadRequired` + +The `loadRequired` method fetches translations based on the current language and namespace settings. Use it on the server to prefetch data and on the client to avoid extra fetches: + +```ts +// Server-side +const translations = JSON.stringify(await tolgee.loadRequired()); + +// Client-side +tolgee.addStaticData(JSON.parse(translations)); +``` + +This approach ensures the client has the translations ready without additional requests. + +### Advanced Prefetching with `loadMatrix` + +For greater control, use `loadMatrix` to prefetch specific languages and namespaces. + +```ts +// Fetch translations for multiple languages and namespaces +const translations = await tolgee.loadMatrix({ + languages: ['en', 'cs'], + namespaces: ['common', 'info'] +}); +``` + +To prefetch all namespaces for a language: + +```ts +await tolgee.loadMatrix({ + languages: ['en'], + namespaces: 'all' +}); +t('app_title', { ns: 'info' }); +``` + +These tools make it simple to fetch and render translations, streamlining the localization process for server-rendered and server components. + +## Breaking changes + +Version 6 just a mild update, so breaking changes are minimal. + +Read a [migration guide](/js-sdk/migration_to_v6) to see what has changed. + +[![React banner](/img/blog/blog-banners/banner-demo.webp)](https://app.tolgee.io/sign_up) From c5a661f1e0a75eefb1a8c2ab191e5476d4fc906a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 14:28:15 +0100 Subject: [PATCH 12/17] feat: blog post about V6 --- blog/2025-01-06-sdk-v6.mdx | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/blog/2025-01-06-sdk-v6.mdx b/blog/2025-01-06-sdk-v6.mdx index 9d4071e4a..696d469d3 100644 --- a/blog/2025-01-06-sdk-v6.mdx +++ b/blog/2025-01-06-sdk-v6.mdx @@ -1,24 +1,19 @@ --- slug: sdk-v6 title: 'Tolgee SDK Version 6 released! 🎉🎉' -description: "Expand globally with Tolgee’s new professional translation feature—easily order, manage, and collaborate with agencies in one platform!" +description: "Supercharge your localization with Tolgee SDK V6! 🚀 Simplified Next.js integration, smarter translation fetching, and minimal breaking changes for a seamless transition! 🌐" image: /img/blog/order-translation/cover-light.webp authors: [sgranat] tags: ['tolgee', 'localization', 'translation'] --- -In new version of Tolgee SDK, we simplify the Next.js integration, we want to provide a smooth experience, -without complicated setup. And now we are one step closer. +With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. 🚀 You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. ✅ -## Using Tolgee as a client for fetching translations - -With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. - ### Fetching Required Translations with `loadRequired` -The `loadRequired` method fetches translations based on the current language and namespace settings. Use it on the server to prefetch data and on the client to avoid extra fetches: +The `loadRequired` method fetches translations based on the current language and namespace settings. Use it on the server to prefetch data and on the client to avoid extra fetches: ```ts // Server-side @@ -28,11 +23,11 @@ const translations = JSON.stringify(await tolgee.loadRequired()); tolgee.addStaticData(JSON.parse(translations)); ``` -This approach ensures the client has the translations ready without additional requests. +This approach ensures the client has the translations ready without additional requests. 🔄✨ -### Advanced Prefetching with `loadMatrix` +### Advanced Prefetching with `loadMatrix` 🌟 -For greater control, use `loadMatrix` to prefetch specific languages and namespaces. +For greater control, use `loadMatrix` to prefetch specific languages and namespaces. ```ts // Fetch translations for multiple languages and namespaces @@ -52,12 +47,13 @@ await tolgee.loadMatrix({ t('app_title', { ns: 'info' }); ``` -These tools make it simple to fetch and render translations, streamlining the localization process for server-rendered and server components. +These tools make it simple to fetch and render translations, streamlining the localization process for server-rendered and server components. ✨🎯 -## Breaking changes +## Breaking changes ⚠️ -Version 6 just a mild update, so breaking changes are minimal. +Version 6 is just a mild update, so breaking changes are minimal. -Read a [migration guide](/js-sdk/migration_to_v6) to see what has changed. +Read the [migration guide](/js-sdk/migration_to_v6) to see what has changed. [![React banner](/img/blog/blog-banners/banner-demo.webp)](https://app.tolgee.io/sign_up) + From 202845dfd510d1195d7f54465a2bb4f23c77eafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 14:33:58 +0100 Subject: [PATCH 13/17] fix: fetching translations grammar + add description --- js-sdk/fetching_translations.mdx | 19 ++++++++++--------- js-sdk/filter_by_tags.mdx | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/js-sdk/fetching_translations.mdx b/js-sdk/fetching_translations.mdx index 3c506ae3b..33754b930 100644 --- a/js-sdk/fetching_translations.mdx +++ b/js-sdk/fetching_translations.mdx @@ -1,16 +1,17 @@ --- id: fetching_translations title: Fetching translations +description: Learn how to use the Tolgee SDK to fetch translation files, prefetch data for server-side rendering, and manage caching strategies effectively. --- :::info New in SDK version 6 ::: -Tolgee SDK can be used to simply load correct translation files from tolgee platform or from static data. -This is primarly useful on the server, when you need to prefetch data and pass them to frontend as a JSON. +Tolgee SDK can be used to load correct translation files simply from the Tolgee platform or static data. +This is primarily useful on the server when you need to prefetch data and pass them to frontend as a JSON. -These methods also load the data to cache, so you can then call `t` function to render the translations. +These methods also load the data to the cache, so you can then call `t` function to render the translations. ## loadRequired @@ -31,7 +32,7 @@ tolgee.addStaticData(translations) ## loadMatrix -Use this method for easy prefetching of user defined records. +Use this method for easy prefetching of user-defined records. ```ts const translations = await tolgee.loadMatrix({ @@ -41,7 +42,7 @@ const translations = await tolgee.loadMatrix({ // loads `en:common`, `en:info`, `cs:common` and `cs:info` records ``` -Let's say we want to load all namespaces on the server so we can render them without loading later. +Let's say we want to load all namespaces on the server so we can render them without loading them later. ```ts await tolgee.loadMatrix({ @@ -49,7 +50,7 @@ await tolgee.loadMatrix({ namespaces: 'all' }) -// now all namespaces are now prefetched for english +// now all namespaces are now prefetched for English t('app_title', { ns: 'info' }) ``` @@ -62,6 +63,6 @@ For this to work, you need to specify [`availableNs`](./api/core_package/options By default these methods always re-fetch the data when called (requests are only de-duped). -If you want to re-use already fetched data from cache, you can pass option `{ useCache: true }`. -However on the server this is not recommended, since if the tolgee instance would live, the data would't be renewed. -Instead we recommend external caching solution (for example next.js caching). \ No newline at end of file +If you want to re-use already fetched data from the cache, you can pass option `{ useCache: true }`. +However, on the server, this is not recommended, because until the tolgee instance is live, the data wouldn't be renewed. +Instead, we recommend an external caching solution (for example next.js caching). \ No newline at end of file diff --git a/js-sdk/filter_by_tags.mdx b/js-sdk/filter_by_tags.mdx index 4b473a6eb..cfc21c69c 100644 --- a/js-sdk/filter_by_tags.mdx +++ b/js-sdk/filter_by_tags.mdx @@ -1,7 +1,7 @@ --- id: filter_by_tags title: Filter keys by tags -description: '' +description: Learn how to configure the Tolgee SDK to filter translation keys by tags, enabling targeted translation loading for different platforms like web and mobile. --- You can configure Tolgee to only use keys with certain tags. This can be useful if you are sharing translations e.g. between multiple platforms (e.g. web and mobile). From 3615e63b4467a376425bd06e03a9cf14404c7ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 16:04:19 +0100 Subject: [PATCH 14/17] fix: fetching translations grammar + add description --- blog/2025-01-06-sdk-v6.mdx | 19 +++++++++++++++++-- static/img/blog/sdk-v6/SDK6-dark.webp | Bin 0 -> 68190 bytes static/img/blog/sdk-v6/SDK6-light.webp | Bin 0 -> 65248 bytes 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 static/img/blog/sdk-v6/SDK6-dark.webp create mode 100644 static/img/blog/sdk-v6/SDK6-light.webp diff --git a/blog/2025-01-06-sdk-v6.mdx b/blog/2025-01-06-sdk-v6.mdx index 696d469d3..684d0eb8d 100644 --- a/blog/2025-01-06-sdk-v6.mdx +++ b/blog/2025-01-06-sdk-v6.mdx @@ -2,11 +2,26 @@ slug: sdk-v6 title: 'Tolgee SDK Version 6 released! 🎉🎉' description: "Supercharge your localization with Tolgee SDK V6! 🚀 Simplified Next.js integration, smarter translation fetching, and minimal breaking changes for a seamless transition! 🌐" -image: /img/blog/order-translation/cover-light.webp +image: /img/blog/sdk-v6/SDK6-light.webp authors: [sgranat] -tags: ['tolgee', 'localization', 'translation'] +tags: ['tolgee', 'sdk', 'release'] --- +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + + + With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. 🚀 You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. ✅ diff --git a/static/img/blog/sdk-v6/SDK6-dark.webp b/static/img/blog/sdk-v6/SDK6-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..44d9fb07bfd7c4121571f7d40872d79e419e6cb0 GIT binary patch literal 68190 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%0Dy(7nIjf6I*z?1Z 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 z zWOiPuaJh*syNZ~)y!A{Y6qyF~BYoj$FskncGTp-8j3QOtC`y@&c|DABNWQrmS9`?+ zJUt#KqUxEUf<8cBJQHa#^MDulWU4lNBGKf7u!EskK1y`lp0}ypKxjCN@B8bv*ofVDc9STIp0Ir<@OXVK4!%eV0-YV z6(vbWmCnL4c}Q=~zFWZ=BWfpqTV>0>Ro%4u<(phe_Y*qHOd$Rb_mtP%o-R?rC#oa# zNlr=Zx=)v!CH5blc}+(+6TYGjAC~4EuKZvtFkZ%ir@DdB*`tGwgdNY=)*8JuMfs#jqs8R>t z#7!~&)G-yqdmbqX%3o8}hgqU)A-;A9<)|`-J4vo8XlU=qdT3=rAwH$;o4!y?0Owo@76Z>HaNoc|GW9S>{4WNa@D0#LMkB300 z|HQl`(KUW{@MK!kQ^4Fvb`PwuYf2>0DYy-#1>ytmr<9i zWayTasW{W{}Y;oC;Mqjvwc$9*ptOy88q(~R7tbcflvk0G2_v3I9>6IUPW+5m$Pz# z>(KgEVSWd6&euCTEVkw&UVZJ!^wk#=4V*@Y|CC)B^77n8q-MoXFmuhJm(QW#{28CZPKZT868H6muA@k z>k#B%#Rs;e6xqd(_s>M#L;8!PHaakWAvPJUx^^uw*W-A}!bE<&#jQAn#nk!@?yZY( zhJ@e1ToaScaxmv4lQ=mRvwIvT3!3xDODc4L=$)olz!xWiHR0R1A5TvQph5_%&s!jc z%p_H*7qZ=kTOK{*vMqcLOXj)7>O+8qgV!I6I*^qUgdX|lmL~AZXrsr>Ztz>?&;9v} zvJ?!n^_TqG$11~Glx;f)3#2O69*pBYC`Eh>8 zC5YTgM@R(~w=VrA^|IV@b7%V#tn@SMF1nMAY=Lr`KYf2=^W~`LF$JG^)5RxcYCc1T zOV^TI`!s|`ZZG}N%*}IHiu}@=_qiWUlFLdBYNKI-p|?@k5}w*Yuw)>~32mZigPqU- zw5M4B0SX}-ZhX#I!oP)8z2YXc`*RJgUUx;awegWl56ZIUt~V`t&I6)ww57^%d3##l@}vZ1tc;58mI zf+5^o!=F$I*CjWb$iqh)S#|e#m|!ih!=<3LSU300P)33RGgh0#QdsLJ$y;2K3)G+c zTRGU=Od_iJ*TQr=f4H%OmWpq6@ygslCbxQm?g}ncjxNO$zq<5z_If{^BZAznmGqFM zdI3D>O^yy?4ke6jY<_S#pzH1>D@e<`D5{7N0o+ru^!AX~Y3Ch0HzMl?rd%z~?TEpS zmZaKe>5%G$aq(G>;i7KPSkeQ5`=^H6DrW?&v8Im2`<%TEA?zu`MuiMT$4X&ydlc6* z^{5~KED;2vXg;lI(n1@j=Ap&!<)w#)$nxX7k#=cg>pX5w)tUYmU3y~SMtACeYok3% z#5%6?pON@rz#0_xTMJbIG!)nH)B&%T-qYE(Nu~zFgP<#7wmDZ1g?WiDKS^D1y&9ww z3J?6AUG(2a!{ZrMyZWp0_ooUU;50%wRp5*brT~PU1!qV#&!|PgYdhO+&S;_oH*ygX zEd_?ZaNj0B+DP-CM}-WzSacjzkYePF&3b9|sEgQ!N}pxTB{*Pyz_5t~^Hbo@ve{`Eu?MhrRZSs?-V(F4?e-c}Nn?21FQU`S? zHYRAMfYD=DM|ATkxOAI0?Kl-Fc0~SZkBxE+4#A%Z&^#IY;iM%Bb5gq+C?uffU#w0p z@XPujU$u{;be-WORCA}gNe;n)qP$&h^>d#b3}XjWX0r2lN(8xXk}}CP)~`}#-Wq?aOC(+G@UG<^gFSH5nT30ssSVxmQp+tP z%7f+?5!&isc>Fb_?tdhcz8eaOFE(q{XuL%N+@fFE_AT~Vi#H2F8=DSszTvIoWs`WN z6ws`N+miCHm|dVBTOIkR`bpB!2Ke`AejASH=A660=<}@`<0l^RZ$^bZ+C+)7dAg&s zD~R3aJ9?X&ugYe#<@!4+r#FkWh1qju=D?K;qUf|Uk)jDX@N`}0(51pAJ-7xc)ngh% zQVZ4Y8JuZ6&p!WY?5@{G6l_pOGr>kYZ$}DWAgSjsLl$H>=pgZn>#^&(js*zEl8U2bKggm9 zO+vg8Z1`fP7MND2anLDe)R4|%-pN(LBU6*H6z>W`H@i>hS)<>9*exE;6D+l-hzbzs zRTF>^z0;I(KMhKSw5*L%PV77U{xM;loevT${YtN-0x4^Zei>Ou z&DVc6LZs5*IXUALriR?M$GtRFAdHBZaQ!c;JXQQeL+E}w%paFnMLc9;KLS(ss!mc-blKC!@7 zpH-yg<3Ai={&e+Oh4@BcLSMw<%!zp{SeQ93IFGrxrKI9T&Pj^HoE|Pk_9pSixYyT z7T#0jlvN_WlQ%rEw~+Aov)R2z z6J=n~bgjimAU-7WvG)?>gnK`(0z#%8kqIBn82bb*Ww!dUFqzjrx`<}!`U#2il&2${ zQ4`Drgy*+dw-$mr6dRK)XHZ3javh>Rwsu5$6N?)kxbdVa=k$gpC>wiVdnkf*N*!>(LCX?)e zTo8qAx5A0|(HH{R7e#$nMT~8#;OuxP9)nwd(W9CT!VZ5Ll{Q+0EN4$Zw-eD&GHsDP zO*sLi z7>0@`OO?c$&P5H`Y27{;%eD*y5e?QjS&V#qgsvq2yI2-c8y z6`^9xprn2U$NtR(C-H-yKmcw}Nih$N*)3@$UBdixY~|iPmKH5TeX9sm>cv2GYD99o zPXW8V_gBnH#N6Qz@Q>Bgps&|_ zQIRqzz<>nYiP&O6z8?%TnGC7=oi21Hb~)B3jd$B8gZTjU2tmBpklZn295`u$aX#d> z!|9)6!^0nHnGb5&XMR42D@8j8)N=6Er;rl32%IS*oxz!p}QvuXbdWm z{NcDDoO8A485r8W6meyhtDmz0mX4GK3ZfP^yMDOc{jd~64q;pQPF2-p>d22Q_=4D` z?PGKnrO>CrDla2_9@tCwOkq1d7+DU>)nmg-6WJGh|1u#*Xhdbp`!>ITkdRb0ZXXD` zNGWf76w_+`v0jhIE>AG`nwWTpxFi6n&;~bE_ku*NjCK+NCBC{JT`q6TipaExpJsS( zaIZ4(FP2D(P;#$orQ(d;$~NUvh1tl$?6@HdQxzl<4~0mWPNGoDD@RF|C&7Y6iNLJZ z*5A)*v%20#5q=CzOPw}@x!d111Is~jiaKRK6b*eV4g7x_s|HNYlT=Us0XLk6bB93u zt2HDwS_{UEB{k|IRGU8nxiEM{$c?{gnN7>W_~>lh5#R%InV|-M8YzY9@eH@)T@Ct# z*~N;i{;}xq8}fH^9@NgnZ59=BF1ej&B%RoLkeqA3nbv3 zUaVfe2T-kJ`vsh^cz1G$)ETeoX2fx$zq9~RpVeW8dE0Fu7LaO zK}O|Bh^3om*bOCvIZ9|vO4O0ST>b7W{j$BHVj z5EvY6sg4IIeBkLR8e4Z^wyAN&0M(?zw8V=kR{B>}bLM<`sLeE#mJlOr)6sCGch>Lu z;HB+iGb2AlJ8+Sx8*Qag90Q@_`J;02ilR=*#@P*lNL(EuGY_A? zwnldGymTf`h@{-^m~y=u_3E+9O#&e=CI2#J+rmF?SB9|97^A1yf2LKy{ZifIBURZvbw+b+|BQ4VB{cwtr z%t$b2U=kRP+EsbtvV4_fGTIY~o2YQ(0FTH#)3vg^Y_iNqAnKy!(q3Q5Xs>u=m{r8d z6`gW?y?r!O8~<{LlP%g<$CRfL)PngttA%J*B&fLOYw28G=*0m&8gCa})IAjS>x+70 zqv4~Lq!wwE@GvK+;dLQIsDQ93@W=$~>$tVPHM;2AuyO}U>aE^#G#+|e^9Rygata!X zSjo>THZLN7JwD~i)zcdp`EQFt-hZL0L=<4yRFP*##e=o1%;;v3ymZ@8p2A%1XqLp| zZ!yQ~@~jFz7!C}*1^iW~>5r@oHE$p>qf~g;j>luMqwH|OalCav4pM(a9g7l(>k(d? zyRHLc`HNC~#Yal2a6feE5sa`M{i{-Yf(MJ|VCdS*1d?aMY4a|Mp&OyT^t~AJQya#h)_p#jCCW)Z4*9^jtN9Od zM_t4qJG&rjn#{LDLa|PyfUtETHs|-LQ>c1UVZ6E)*IRb@TYy`!q;zgIz|kn=b{mD{ z0@Ms4BK=BwF-8ctXp5<6hnd(et7M+~o2jZhJu=!ELqqpAx*!x`wo{_DUu*^hlKWG6 ztob+Ugpxmgpk)l#NzCVw_3ki2mQxe^JviCL1L}98y`w93ZG|@7l;6+c)`lJj@@bou z?WqW&RuVEUE%}(ZKi@<-n^7D;wQgty!Jx6A*9~LjW19}+*|><4EaFS9xdHsbfem@% zTwiB*P7iP5w;I4$z{aJLXHYWIYs_B4j>e88s5mUe(j{hV*2(l^ajcrX@$`xZ{qWRc zED?`xPint?)*fag7CE`f;AtvF-A-f|N`ud);BO^_mblz=?MNs&iKB_Z{_ynu;3}U) z^yJvuRY+(pXbep<4NU0E01+lE;eB%$>euk0R^j!&P_z znHg(kqc8nv-3x1IPK%^*{G5ty`A=w`BC6yE4~fo|a-6AYS{i(th&Zm%1`%T67$je`Rw(M&CqAK08Nnl&Va>s}vvz4}`aib3{ao z?-nhJZsdVksBq?Z3`4Tm8!Y;P55@1nB+){ivyf13FpeMBsOi1EEC>4AjGcfn5)9Hm z5-B2kp&qp{Rt=sEgmC!xi~GiQLm~SWPL~Pr9s*+13R$tR;ip;P1b|&svsj~|g|fB{ z{#i~P-~H`ITvV0A`8j-F&zN^V zIvTsYTlk~qI7$u6A3t}%fH1*ZALp&^ry`uROhVwnh(CJ{L8^pSs76J{75ItqNYsCw z4=q!E{jyvsMfeAUw{!en!t^N+J_yzQb1z zfOQL=t3NEib9W6#@qVeg_dVJBY&N{m*V1A$Rh1WJ<+c>GUO2He&QpbQeG4F)g~|e% zQ#^4`XsD_X4j=hp$rF;8cg8M1+168-@(1I^*u^(Ixm8v%bQ!Z|lD65`Zvj&3to71& zPrttvgy)g99QH=ucwagvp}UBkrBJu`cMcG_A-PTWcTV;!^AUDf3Qox&qM(85M_7Ju zw2NHOn6K%vmYVJ4o=hP?8dzG#;5`=~WDAzVA4|{k2}q1R{~E%%fN?osHCNHM;(oe_ zQFD$0T_9xs>qZ3crJOcD#rNL!XWXMT_GQQx_lX8+-Y|n=er5hCJ>)^Qs|)*9)C%K| zFCCU)EgG0EG8EGJkv*@T{Wu{$xCF?UjHW zu@)=<_%Q;!jf*^bk3@S(cG{OxSt+G;Fs9M8e8#eY4f+#TW4UMX;kR>(d`k$NeKxP3 z%FBE0(&=Lwo4HUh@Gl3xwl9lCj5G{PDjKj|N=DtPOpYs&_6xRx+cJtyyXRU-aV-J+ zrBnZ;GT5Uyk2VoN9G3h*Z-d7L0~Z39WvRwnZ+4IL^Ek~%j>dx6R4x9a?ipt#8LHXx zptNd)wA)S_czIa+jHICmcMK;|Z`YWDeR1f$>#%Gy!}lqYVE4bjcb*$54vnnKsce*Q6t-EXGR*p5(oF%gN9u0?XN?jqUlifb_eZq4H^15iM-YJo}iC?mD^t$iaQzL<|X+*BV(%UJ5mBu zo2u0FD_Q41_J09f_$TqZ`}J+0F12X9R5OO9JL@fRr7he|_Xs(0{2p7$OVc=-2*Gcr zakIB*3)MgPXa;PXFAu{2%Q4Yh>3FGJDkR-Qu*4;}#k&y+8|>gUB>npgu0g$OGO{8~ zJsYdFMRskk&-9-AJ8I)BZ8?y<4c{ajG6yt^_vl&H^-Y*r`G<2 z7bTM!b#qy5I1?1s)@&M&gKuPV7Py?0<-bPy|Kcjm2aJH(h+Mx&yDbsZamW317aG2f z7uXhVyiV~kEW)E=}9di&?uOB93sm zD$iJ~F?C>Z*$`upS{j)n)^33-6u?TB=9AihOJp0NgWBpvh3{)%g&6!Q;tD zs|gWO2IdomDRz@Xs~Hxe)Uhdo3W$Ox5k*NiY7KWxpRZrP|9Sls+vu z0hFoh6oEe6I0&4VTmGQ)XE%jZnxg%-8aoagD=lr0^IOn=)J`yK=gMmr82KEz6JMg) zk{c>R10%WpZ=9(vXM2Qkh(?yXL9!ZiiavqE!P>OhT#w`4m2Kwp;?;0rV2?xz2Y9U? z+sI|sE6CJ(p2tbV)X3rBvGtkU3%nuXSRiyAWd2bgIt&5&PWUJBD4TDl)>l9@1L`QkZXn!0u8Y|&q9x7GpZHM zotTV1kD`*8L*Wk*99fpZQ{@Cl2KiB%-NS!>qPR6gxQ*|PZ7n(cnwp&Tic&f3g}FOr zWkBYg!Y4KS&<@hrQP^zK&S^?+%RQxKiKHJRv%jrRqVN`g?Wms{A~m7t$_1v#MwAkl zQHFQPSN6N0h3CH^-QNcvczwvuiRP5xpYi7Y>q*S41KmZW3+TRJ=z;NcpB#oBnz@*e zV5O|#bI5oI*lPvsZNL%F@40&m*nz4GB86I-Hc&=2k z6TexcL>FF#ZP*jUKt{W5Gfz9-K%>2-}h0;P< zgHAKt3ofg5zw<`=l4NJ9=|38GnoxsCc-3g@h#zjVEkk*0fv@6(C5EO__**|Znwhl3 zR#m${J*Bub_A4+DI*}cwl3--ihB|Ok1I8)#q6Jfm$ms3JH`aj(2E7b=s-0d(hw;3kJ8xC8%RgO$elO{P#-0bJk6uNH@8h&MGUI zp`1job|#$op<-c=C%gjdHmOVSXm+B#45n3J%~_v*Iq(&A8qu)=#XKH02SV7w!6*u3 zi9wWur>$%nP#M8Y?|gLx(qc;}56P1hm3o0-TwXUlry$i_WlN)FHPS4>y$ z8o6O?(dD%(q;(z9n$Lnqc+!&M=7P=ig;ttJ@@=-kSOMDRrg=XX37+R8~p`KELT<+c)PUWwo+YL zaSoW7J^~1t`QSWklsS_nyP&ZQr|6{Vb%NB#jPb;V1mzmtJ3hrP<*=$+G&qngDYQ$>x*gOT%%IceDz_sUq_fyUPCq|u9sQ6z9NTNiY7 zfxE>zB@tS>Sy%jmkO{}>E~bcwf2)MgpZYiuP%iL$YYqG345U-3#syQcue#lnDVb0q za^Zq~g82_?y!WHq>NJw8G}t(nV;(A>Sb(9|C)CmR@XwiL!GFrvHb;c@;kD!EZn#0L zF%cox{}Ff^B%y<;6h8Hy1}m6QxY;}SFl;f0AAOs(;wBN6stkLTjrA4Q-k|jMOD2fs zy`%*H0A@39x$PcL1N3%?Xt~tdK>%_siCQHv4?N;V?`GY`wLaGagYbT3#SwpMZltx(=hg>YwFKLurXa#L%oyJq8P1g|#FC84GfYm%( zoK+xqipZ~+f?=`}Fjaw{?Mr|{ zqoahyZ4uvH2z-P3fCJ4;H(q)u7~=f;dT@7;8ExxH=3uM_hV)}~DTztPlO^S5 zF30VlXz9ZYXxFa(IIVu#p~!OKEB5VXwa#yC-5#F;ZX-_q@&sCQn&6R>1bDxtbtwMl zwEf+6%5656KcFI^kS~GlRXsW!+5f=R|Lw%ep$Atd82hGNFRL!GGLT4Jb!5{>*9i zF9K9Apk&e!?#G8f(r-wyX3Tjgzv>#Tqms-&iSiaRJ8s)|N^lecs<_FzpbG`wDh;X{ z>UWHcl}3N^S*ecSeLTV!bi#up))Km%Gh#mVR=X)yVcgJmE3t>F$Pgu*@F?FHDDtRk#vZl#V50C zY^sIaN5JTNNTS`qA`T+4v7!O07ujH9&xe3QEg0zJ7oh5;Qw`#D#m&g4<1RKr!9;cR zrNoil35r?IMc<7OIq`ynNNQ9zL5;guZZDncG7&m9`p7|Xbxk6qDD$e)97mf~vkOST zJtv%hb9mFsN|8>?#yO>V6w66C;(V3=rMvHq>j7 zb?s2UmU*%1O+-Mz+KtJ`T^QiXg3tdu@y`$8kj`3w`9u4}c;ah-QaP}`myc6NB&%CtUlN#~Q`&CzJ zi|MvW602iGg%W0F(_%$9}meT|_|sT=Tk&SRQ8M8H*Y-IQJ^U+kbVT8&aSkan@n z6b%tEis(8A1G=~$l$!43(86srFS9AOH0D{yKb$rAvaQ+m4)LDh`rnMF0o(xa{CH~Z zp`tQhSevi@-$}99MY}28U3PEaeK!$KfWdmwZCMN+Q~D?k;L%BYf@~8zH}gJZk*L~? zW`_NOPnq-n3F!mKdTyRZM3_t+?r@II2gBXo&2%%TQ&(T@JhS>BT$GQB8K&r2Q~#v* z{#fo5mdRzJAp(6U>c9WB62awX>d(WLK&z30EEPC`NypU{@`Y~m+Y^w` zImMtSB_>fI?Gy)eubiLo???J@jv$Wg0w=Dfz zpW*V30`JQ3Fj!6aq@d=|leUxiBa*xhh;;MTckMK3+cFzVcTYDI*Vbm+a)_Mw>6&(S!s|mUa9gkL`7A*v|KG{b>VksZ|Z1PMko0QMr}=TAiLX@;mZU z8$y?caDi{hL6I+C*H9-<>GIYuVo%dHsiNb-W1zd~*&{h;RMNXVi9Vc3$o6U*UI0-% zIZQ@HzZ#y^h|#FXo)Jia*f))DhS(JdEl*L1vzJ<&zvw4TfAe131i!gN6K@vmS6p5^ z{xDs{dLz%R+9aOr3pi0KEl@Y3ZrDrhRIqj!!6(=g#O&^=Jj#mo#5y;?Hs?`QXg0WF z#CFKIjZp!)cI>TD$Us|)u}R53QegGTtGs;+@HIuG<1;N8J__=N{Sd*PK3mV^3c*#h zL*TgWhuC#^T|Qj(yvZB1Jd}&l3(qk*jke8|g|Sq0U^j<`~1e(mN4NrA5Ja z%NGXgnJbhBpJAe){wZuO29$IyN7p3?3L4y}bIpqq>qSu4!Z2j>T%8dB-|;(Ao(6J3 z5*}5A(8kah9=mi=pZcH9j{G$m9_kw5&)!qA`)dyBgArnJ2;yW^JZ~2FS)tEBnzf5^ zhH=aiMuBJ|`-DX&w3x1NynZ4LTmcadgutfBU)Rz>sAg-{B%o-@{SUW+;+$^tu$LoU z_Gm2EWYFYez3}>Ll+B-nBKkg^|c(A5^OvwJ82*x7!`HiKo*075n96uMH;1Rb_!oXita=&ulYnv@`+*--9dV=RhA$OJ93h>CQ zZQQa6Cl*f($9arTRdLGj>Y)4_`Xk~fFemCyRS)9I53$Z3(D|M8>|wB7)l>9!w|#-R z;{Opv75;0ilY7?vdBVXG;%j73f_sFwypk&n5k9D_ync@S#C#Wk`>qrr>84#jO=8{L zY0EO7_pj67zh`Th*7<;%8k;%4ZjLztPJvcxPHW~HU9e*9JXw}lprHDr!$8eiiqkmD|Vjt zt9bQX;D#6Tcl&`h(PZ^RAf zE(8M40K9YD?crIbv*dsklN)to%k?iN|7(R=Jk8(%8j1lZf`fwo!A;qRF%z!^FrH?& zWi=t~=5{qZ;`wz>XrNLFck{o??uK}a_7_lT-WVN++P~s9-G3-mXpi_~49US5wPwyB zSCi;5eeX$ptqY)vvwuq*mITMGOP~V|(}&xdzVj=&eiafI>>h*ypqsnWDF3Ks!S7+m z%&TiOK_$#_Z|h1neCi9!?2XyM^SS+}r<|p=+Wf z_&R{OrG{C2=+d(W-^QkzZPFpln7TXU78(|!(aS#|4t`j>xcs5*@Jpl>AOlpb)r3r^ zEeH6cQ437iq6%RtN@&$+eafD!$P6smh-38mxZMQF-J=DCn%kkhq{h9(od<|@8owRy zNg^gREys5*a9OZ=|8+0yPh)lD;jx8!h+h8k?s6y85S;SDfY-^Zk4Rg_N~vG_im4Ht z?0q<|dEw>?yW2z_dbFgn`eDU+UD|II)48=T^Ir4BvhX-2WO-vFkd6X8T6`p~7LRJU zXdxmK)~x9MlhDoAHj2OJtpVUv4yOf`O(cLEciF(D6R;4Uj;Y9B|BIioTjt4LFkly- z)wh$;g2BITiwik8GAU(xk3nYql8>8&$OlQPF;$sC1WVUA$`sLIv@y1zC1Y3pls7@C zYIZF2S*YEB2z2AA-fUvl$2~k9Ia^qTVQo!ZEM=rAu%#xU7mpcTO~H@$LS<8<^YdC; z4w)G5Mu^-HTEcM^lBlNYtQ2lp*Tl~j&=olf#xfyl8!ry*O9bDyYEzD+8S|QxXdZ=V zLWD~=oeF24bF@GqNT2N)D4b1c{rx__hHt3fip;v|u-^_)A~0;FNe0R1 zz19r?%jixu93bUJ3ziRk;^KL^AHL4sZvxUUf|eCH6QQLmcpG+5m_gF4DhP=r?Q)Ol z?JEUgp%k5pfHp`of*zBI_@*ivvAtVGH<1x9l?|?jSHx`aaLUMnpZzZ6%IGZBbF}REK45gGb&_b^?|9GLjkij$WZab<^k`Zn? z9OsP@4Am;TkreqCFPzSybGs>lN`m^>_v<}Hc8i}n-W!2}g1LS_{5}qjD`tMYa_lwc z^B2#h02W)9&rqVKJ$sgRi0(`30!>C+{*D?nYT~;sO8tF1(`t#tSQIek}<;lmuO)^H&-PJy* zyE{*wD3xgg>?P+1-Sah{ zJ0Y{+HQhbP=}6ItIN#whLX4cO?EeRgP>Gr`CgoLSW;sH(}xcs;NWNjsw=?TfHrFv7l*$v&H-9!>M^7zK997v z!7mEKsjosAhi|cBunJRts3hP27S(f8igf=p)4;VY1nBe&P@EkKzQJJqVTs&~C!A1J zaM6_Fbc+w^ecVKVNmNAIZ$AeqTF`?bj0~18Pu3S@PWVC|XrOb^b1CBWy}qn5L)D5C z{_bu+YDp`W^~VjX+UEk!yZ@4GUi|xRVq}fk#AbWnSt*I*4_6h)u%0mnEL5_~5G)Au ze2;iyIEy0w6(h5WqlM~)=5-@HcK*Z#>c8*qvJrLa`jYkJHT3w)X1rcwH-F&D1Of$?I`rpaZq?}yI^^9qrm<>^3 zX8np>XOv^Np3-S=DwR(YQ*?}oUOiQsmMAAau}7@-!KH#{Z=n&}AxE|1R2^6uu~o4l z0y#X>mHLRPX@7e9MlWi_s}!cUW(01R1o_wE_;jAQ<3cuoX;nRCOL9Jrx=hnzn_mJnf z>iJWcCo7ecP9Y0vc<|V1m^QIPl|$}%>XPUVPF`rrn>L;m`FNHz6%uUg9vRlubpKlI zDGUtheVUwhAMMe$4aJQ=IqC1IkzE7-Y6Q%SeME~41C)7*pIo#W!bjHfqy?WNeg}!A zT6BRf?NLlms$e3N0H)q%N4V+}?USIs84PE!mMgy4&Jl07007VZ)%cl)r_|!? z!dWwrJtwMruaCF6F?W~?GX)&38M$0BP4ii4IlkVGA=2}q3?hd3k5(B%x+rod!Uk@Z zkJ>RCU*ibO{|Ix^17WMZ=o_7o7l2pR>mbiCMrCj4foqJL#vN5Q^IKnae6oQ@wyxpQ zCw%qz$F!o-eOMb#z>vkW;9XH>Cq9tnxu)}ajTVHhduJZ?-Cs|?ZE4(}McV~jH!m_# zld=x{k_Y+gpZH*R--J0r`$^90istEdHf7y9S@B)DRtuYF~z*8W8|L9w~XgF7VtMRCBs zfW)C^M($*e;-tvnZ|$}4S`J$a`mbQz?X&ov-`}v-YHdDHr9hd8;;>0NXMRa!8g^av z190hWfsxGsbzi(v*s&@xTozTF!x^(B1X`jT8M@_T|St!1s` z^Zz9_%a5$$-f*M5Sf8@2!~wy~p(FODr|Fa=D6WwsT1Uyu{EVmB=Sh|CrEiZCd737n zXJWbCE=vvmJ8D_tAOAWFXv|Y%tM6q!IN(_fB#jmOJ`Fk#+hYUCU+a^a8&^xBAJj+1 zC6Jm398pnAj&hEXnX47QdJ*rM9-m4ovnMDpV>SPy#>90cg%f?1>K^C}XCc5~<8k_z zRz`S(-HBFmp6aTvBtSeRo0b4eUsh{>;rQ@KdBB85=yDoGfrKVS;$_R5Bs~^<3M;`8 z4_HBv19&2c_4IU>i1o{04ba<_EJSnpXC|LZJ*`rT>M+C8nT3A%r68p*VaJh-_(N0~hD#H%)4KAo~Ul8)76bPic_0^d5wh|N`7`%a&jBcCAGsP z=yE+KYtZgF!{_fs%F8N4VP2#vbMc;KQY!JyjAnv3OWoi(N$j$RMAG`*D4Z(AcB6z5 z8)Zv%9O#rC*>_r=3EoL1-{Yf6W>VfJGkWS-a7L(6mdQ>lIek`lK6sv{9m52P#p(U> zG??d@59%EmBfz#Ezd8({e|x|d*~AGR*J&bpCV$BpTp1jH&cFQ^NxJeb5Ns7m87F1) zvb-@qc5Ilf?n#JWTK){wop<(vi`qm3!vNHLQr}{}OY&Fsx)}e=YgFoQWtgKLuR?0^ zBBRbV<%RR(=RYBMSpAiA7B+Feg+yYf>ZzI08sEw5)-O5MW=*UVU*An($>v`+k4rxh z*hCXTluaz(ZuHmTG_$RxUz2%5J;~CQ&_XyqwZHxn6u?CqfHQB51v0(na=7S~vc1m4 z|EZ9uG~4FAzJU4cU?wgPqX`b~iV`$f!a`Qg^rIJ)Y^4m|*zqQ`4JR$;#M(9&X9|1> z(4d~OEq-+?qCn??H^A!?y=4uY5Tv5ayfXT zt=$)ZJXWt4<}(@~t4AV-Wfs!Dxp|f`usXv$EDF7eT*g9BK%nXThXY5n<8YV1g5Eke zJBo#CVXipRbHARxV_=rhAS%9$9hak?2WrlG*JcbUe2@rK`T!b(L^;GKVUyc$FPK;>T|cwQdP)r8sr1+uFq8 z+)7cOYbS{dz^F}TGoBA6$-E*ynXTFgC=rj;0GhAHBNexv3F;{x!Y=QKbQAVBfbVOj z+fzp|xGXzmnFW;QEEk1V<}nx@Hp#3ZQ~e>zviYNJ5B zN;Z0tY9~$%r9F;N*B24$vAVHL%K5g$RO%Mt{~9h zgwp??++pqblKsPOk00=qZwEf-iAU9!$wGK}^iRq0*yPK2)yv~u>VQPTysYMDWBc2h zFXoD*IyhSdyp`B_u$y4*t3uc?8T!=!qFrLvD|x~mvEo*i&JyFb$2U%FA!}p|v8r|y zd@UAEMTeP{bw6;y*mcbxEv{RH5>E*hna9ls9&AqH0{H0Z7Rf3c-e`AKQ6b zJbSI{*=@!PwwdV9cn(q|3#t;Z^O`Wpv!j~zTnWDQ1?6ALOk^45m|@;%cPS4PUNO~= zpY8_vu1luEL51}}Pw1QTMj6IJQP0bN1N}C1A-f4~yXv6MmDEpj$o?+BG_AFKYP4iH z{DNwiJ3K{%*4Rq?gO9cw=|&KKaB5KV>*yb`)OW=idNtQ%OoRWhEeQlR?#8g@CVKel z_;%}AB+6qQ^`HVVMXI4UP6=r(&P|`_g<6|03?}5mD>F{|Hwzvx5>X z4UZH7=hg|#6S+Z-l_r%5oy7{rllqdJyv;MQi#NoZq}+xJ>|v8DsnT*!fqz2nxKf`$hMe&bOY0O&y>+sl}PdXOA8Tw zr$%RZUZG^hW0{|0WVh@hl#>`TxhhByReCiTb>SA^iTGw+K;IkEkPVK1fR)g-l`*n4 zc=mUUHt(N;V%t9LZ7fYo`b#vzWjV1TkLrFmep!wF|Kcl82N~JGFV5P;@2jbOdH=~E z-&hVrWn4&0IXpWId_~&`bd#LhJR_%!rQSdj*aB|IZnY%G{~QkGp~%TzmY)m9Jad?D z3Fycx&)!?2P?|j(`)B29tAD_t9Gb!k_c(yNQF4{8%mfwyiCxCXwdg_)-XS{)y4^Jzb!11R7azSskVhoikYQ!DX!4KOPtU+JbqtOm-=u8 zSsZ|(;)>|h4Eihb!)0(ZZ{eM4!X5>^bDH($m9Y%I6GeqGWA?z9YZG+X-jI-2Q%mnx zArwdWbeUE`2ni3?Ri8*=`9l2wW*M&q^UY>lC|Yd8i!bhA^dPN{g%e~#R&W!RE_xIQoN1GuL4OpyOe zx*_9dNsL$zV97g{9cULSjF+!Se#I=mh}C1?Qh#c8LF6 zI5hl01DPcO@i?I7$D<&BW8@3>B0u@}cVSf}s zbl}oVfpWRoe$z?zD~QI`N95z=XTFcTX!m`6+^+w3Tp~rJt}(3e+~1iG)RIEriSGH* zM+K1hq!laEuX9TeQ*t$%0%ni1lR{i(dpLIK`n@2e7uB@(Fb$VXPW+BFczUfxmVB-g z$AoGBd65hYFs0vBV$E|p0fpb0vR0Ssux9V9p#}={9>`#Q7zLt?lObMnc((byHehHg ztV%B<8yi6Wt_Apd=9IARbMxj5w`TKP_Ef)@N=zl9{-jjZBgjj>{9d|Et=@0!&3I%Bgw?)`c2%OoANu%`7zNX$>|ET>$?8zf$)<_FUF7}H z7?o39T+GeFqFLhD7csB4E$mRaD`wh17dPr%uh33q$85}jnHoT$WQ0c)$_04gkN3#v zx)|QZV_2P82(N&~w*kv(cKYau z{ySb7((@TAA^!~OYt6|j;(cmIq4OSb@p0mxfeTbDj6?zt)x8((<0S94I>VIUlMzB{ zk&q%vUG&7!YL}d)O2p)sSnE{@nJK|KY7Wh|JSd`8YRBpNneNtYQ%XTr6>|D*XBqZi z(0=5WXBknpjJ$M-KaY8|vffElRooe{M@O1L90T7ncD_n0XIG}2*ip{xAr$JJMmIt` zzf_|CnDKP(6f8P7tN@C)ip5)v0k=Yv9J6`Zp9JvlmgoD z;i5?9Hi=?b>B^PyZW}F=@3K=>$11yZ#%!a|bs-rzY#Z^Y!*zmvNqda_Pv()-!xd?2;G?1@_ zwu{5`VQ#ho#!o#mWugRcOz*+JI`4(wb={{$&pAofn^T9yz)R<|MguA#f}9Pjhwj?` z?S=U~QUo0BB%Ie2LNWFLc2A|7m()S`Qer7D%bm{;gs#RJzdW%zo-XHd%xU&P6nFe8 z3+E+0KU;>M1%55qkagrrWJH3t z!!wX{xl}BfMnk#!mf4`1GmCkLevCAPikN@fB^b)8CSN*4x@)7o-GA~!D~B(gS4qKt zxGYfLl{DxuyM*Vik7i$}LisZ_K3E=Ftl*y)yuL4+889|fpQrtz}O%Ro`afWbyfUbLXRpxs+dFUPca;sT(s}3E0jvXlvmZY zKk#i#5j;N_OO7Uxg_@Ou>ssPZrrh6V6oh1((0g|EPm1Jq`n_RY24gO&q-)NdOq)sQ zB5p4k43Vk|0L%*M#D&B$aIkfedPU(YaS2PKbh+s(%cagojQc#SBRN8QJAE}14|6ih zJan0H;TV)=v-cB0U$9Q3N6Ro@*@51GmKCYA12OPc04Uo}d&(KY0SNkKSk($xg+RH8 ztOdB>UhxqC!TfQ7QYvoTWi3WmSW}HS#~UBWF!?V#bS+fs#2I*^OP2)QB2m4;`3qCs zW*Jg=kP=ex&&++ZEOD3N(;*mG=3#V2KqoW#{JZ^_x)9a`W}(Gyx)P*B_s9VHY-N>V z$g91aT^3OpscQQl?6n!@dkS$tk3Lwj!{_Jgf&#g{PZ!h<{vt zz*dC8Uj6rG5!Uhxd)q1@S0j42S9eyTs_qvg5dJb&t5<5z)RE9nsT+)To{mwoT{wBeP`Qdv$Yur`j+t zGZ`CCG_q-Sf+rl;=1X0)ZK>(k;>jO`0Gng2BAZZ!0j&B_T>oR6#X_X;`-m%Xo# z{q3cYg4zV|zLYP@Fh5+9YbbGkr*x5OcA&^zff?Ki+eM{kU|4PEefOCXI2*ks&>>F4 znBmDlX9bSe(1njVwkef`j8U5CV7|QLtYMElP}Ui&`+>3r(<9}Ol0Mu6sFQ<%4n@8F z;QAFe6ORe5H!n{N0{D_O^_UG}7TdBFJK8RM$c%j*09d*9UC2#hH;f!_ECGvVdV}by zyd&Ox4M1jYLIzQ6dHoubR!GD0Tqe#?mIU)5C1WzQA_%9Vr5l_JK&qY6H0|cm(?tVS zH)G-CU{^A91B9{b#TSArj?E>)+^?J@;$}CNe?zGh4gy(Rej>vtT1=8j3@A2R42xF` z9}R;F5IMVhEaK=NWj(>Qh6@pvuH*$V$feD@uKxVy;qMl(zFll{iZq|FDQtmJj_;faP@%atGR?tlH>VI=df}fN5 zG7GZ7Ug>rd;TiHsKW@`EnWVt_iF8Z{?v!y$yO!)%h>ORq>&PrMZlfYOj^P$QzO;Gl z56fUrCt!3vIpY*TYD4S)cjiQ^jv$k|W(7%MhP5h^KOz5Eg{z8%_WB+rGPPtBL32lN zMLEIeF#V|GPQ&`|6Q<(#sg!}+?Y#U($!)Rch{8~+45p@gXYOweW*E!KiCjAJtK*XX z#yYi8MaruKYA~^~!GGcF%}~R5I*~ix zKFIJHqU)$q)2rAl9kgbg?B~-oE^>&iamg={xrZnV-f}Vw+P&awCA}DSkAs?aGKo7#*MONlK{9qqpIQgHA)gy<(!35b~6d0%Ml=1 z8oVsWZK-a}2CuMCp|_@YDffg=h&A2|Xq^G;lI;{m(qeVDp|tz|=mr~T10N#iq(|lQ zKSe`wY%=%icCCw!>PhJA?=&D6%`8I=cNXD%xq0_iHc zpn(JYaw>sE0}At}^0J;HD=@wJD$+&?Xl(UM?Ei>xO|pklVP3$xp%mUf$Rk#23HoU=r0+;-7%!h0v@obE)a}Ae7F}3%drMNaMm^;87N*7O~Vo^olfpE8)CH`$0mH*EZsWh-{A2VVX2 z&FJ5g6g_eNz)JF~w4j9GLNX-aGy(DZ_jHCl`%Rh8_6qhVBEP@A^c%U%w?-QkK)ZJU z``C382s%y!u&Uj_KE44tvKakm0+2fH`97*wbKYymlp2xkv(IT=&Na&!5Auf8*gEq`b+z2>VL}{d zbuyc7p(8g`6ga5hGf!wdAW!Km&jpw>JI9xCxwQKDn-USVxKD?f8SmiguSDY=GTty{ zy(q@sYIUrU6E(+A`Nd+0gX74WZiTk%H7KK0Uf;z&uj zllSmpHreF25v{t!Sv1>05l)^VKg6-`er>;?y`mf?hQ9w4jcni{FmXJr;>yuWadf&=Kg=C_X~*d;Ycs>M9Te8=4ZD>Md5~rm3(X6G2@S2`s`v-l0oNy{x~^1*r%0wRlOjsz}hUw*W23vT*B!Ky-~s`dw8%&UZf~w zh1r%!r3Y?`+&f@80{zJMn^R1YVq-TaKgC^)H-htkmp~0)j1};D=u!mQKn>f8Uq#@} z?(Qr&p?Sa^lGT~Hw%Iwnhg{%v4uwfZ*d6)jfx^}n4@OCF@v+4u+Yva*)49ZoD4~@y zcQPYcbf6u}V@l)thEQ>NIqIeTr1`{3i_fs}(nR4ABiG81{+ppxzXXpEY#v~-+zg*vtD@5E$sd3c)c4_SUJI`V=5XN- zDY0}Fc;|=nl6pNhkVd0ql$URmvqvTe*ZZ}y7>Xuar907|i#W)K8IW=-7Hx>}`zjmdWqN`i;CUU`l4Du$q?K*V5uvFCab|{3=1>%}W$P^arMOAoaIb9a+ zeE@Jtxa*^&Ne(L*y*^6WBp-6u6h3{6D~0oy?GWk~zVwAnQnFzpm)})a(1i#W?{pdE zX~7TBPia{9t)LFH^c=|EMr=j*KN(ivXvI=zF2(FIb1=X*OG+Hp$``TN*6qzlv?P1J z8+y9!8r9+&(47dyS2|s!B2IdT6a3Q@@b(4m(n&G8Wug#>dJA{g)b$l~Z9d)0`Qn5M zDeLvNH)X}v@%&;QV*Ls9k`-)1_awqy5n_f4{eyeH_M_%(&dwY$n zQ|jb@<7kh;0a=Po0p{&*$bDQ`?tr;*ZE}gN7zEV;aJ2=a*L%iZ_!7He_wkA!K$r8w zh%p92>zZX|Xoe9nSCy{LoIx}T%aM=hO1Hbzmif;a>S(A#b-+l>G-cDYlZ(&DN%3`{ zT11ZEw%r0T%?sebtAUS1>6uh)TRyA%kKajI6D9?CG-m~I42~+JTtgSfxUy>;-VfQD z9U7S$TvwYb!L_B!p;DIFAajif5w??t@C}T-=r4o(3hw)li2u+3^;;z=mL^O{YsUSm z+AjRQN=Rq#kp7!jMum9_=wiHX2V(c=Ok{uwACZX09?tZ<-6)y`(^Jez@{|rikSZ{$ z?jB@T`m;F#w)fE2PX_VHc#&xp_32Bc2wVMoL6L3^QsJ$7xBtSGKjtwmlBJGc%F+U6 z-UVR5NRJixeSnod$E4F1=MUBRkZ>Q;{L;xsrKMAK-Yo}<7=$!1JFSgpe+)VXaFglvi*V5>e_oq^DFF!zSu5Ew> z9<>mazfg!=ClBnK`*F2?j$|VK=H*{nYSZ6PrOn_x3c4Q-V7?zqtZkDqCHn*^{HJoT zUztT?BKV`Nxusy_L=_arstMWWxs#7TO?FW1P%iddl$>XTv*MG;4Xeg3DDuIfGoo4Y z?KOimq)|@~Vtj-{E~~-Kg^Zlhu^Kme5r5$Kq;@T@br@&2CRWoYH-CY zvMCa7H41$@FvkDOP!G(PRGPSLXUX$6RBTSoi!-ym&JY3rznZ*;-pufIk50OyR%LnB zw+^O_CjKQRfJttTsz~U>$_Nvl6S$Bt(>A_W8i&*oYZ>0|l~5;q|1NVn((O!_5Ooxd z?oih;JFm2U+A#MmVBOeF#jnfT)V}4VCYWE{eU;;Oo_E+g8b#$`b za5^}OWz=2l!6y4Z$I4R13Mo73Hk!O}DhcyP@)Bar`55cg#{@1+LCoypM7=Xl*9I|J zULLh@J_!Mxq&9sL!>C}uUp~QoH*A3SmeAje)8Uf!Nb1nA*$e|c8w+|})1W^-^zxso zO0^HUd4Ufc6_#Fm8o0FS8_Yw3lJWdobkfwOgr9RZ(;Kds3!bbOy0E;sJaBZcSknk{ z`}4mLKb>j-cIt}m8g$}E;|rTr0LE2^wCX1RC^q?|fQzJ*h(4KC>DAGy?Q?|*&C%pY ztulgzRCvY-us1duM+s3ubGgAQ9gxXSWgVR{mnQM0Ss#lIr;P5DhwIT92&;5{i}-`> zK#!DI&8~SR-jIhUAG3u@$4`_xJYMC`ADT$mBpjJVUSM!5A{{|YUyw;7Bn%U%FwaO$ z6BC|UM%n8yg&AZJbL1zv=*sRnH?U9jm;3YoCF~@p7gfAoHXJ0Cr}>Fg-}~v86xUp4U#X^uwJ=c%1`TXwZo)1Q(U3?w#T zTEqBhm%k__u}?Nuw(mzp@#>yMG4s3|XnYg&!Su#QPrsByiJr?@%GFGSkVjZ^50nJc zu7Wkf%HwPT5>`8xTQDWW9o{T7=x@a%zPvq%eiWZ%z0xrmxM&{1Lv=5qeLZ+t$oE8G z->6o!)n`IpU9*5=gAE5vx|4!aJe9R zgMQeBf~XP4fzbL6sMDtZ@3TdD+7%GopV9$ZR3d{|9K#*JxTDOdRoAswQL`I zjko8V-&`V_XH1IuEJncXk&5kFeI8i(^LsZPc7dgYedWILri}L7v!wLA7_|s;Cu(*r zQDR=}o_gT(e&HXs{X=BRO~O*}&ut0;7#T`fl&}|j4)^o&f1!m;Ho>{xqoG{*jRtT$ z%jX51=95)IlN_}S;U{>R=^e~u)v132eA)C{ou|mGQ6A5dzRILAlP}m zC&}Lwd*3{WTIzzVOtK=|u1)Kpa*U-zzTeg@)}M1oJTcqq?rt*66YzgL{V;Lair0Nu zH`x*d3JHq{s+6yhDZ2g
S;f>Jx|G0zwxY? zsv_BssU&$#&AJbUcmO>>!oP&ok|GQ8Hqah%%Lr$GdE)jFEfH`FTEoU2V!C{wQ%s;j zQmImES$f>v&gWAUw?$>EZBhRM@@eq_E62=jGIhD<5-1g6GYG?#O<5aL6ku?VixbhR z#EwiRz5V_ET)$+OB!cmr4Y(?B8iQG5G`5n_N^%O4E9VTP{JC%H z3$0-5c)%cF8uH7}_XM*1R9{o3Niv7@Y^P>C*{N`~?DLC7>a2OV0}%z(!oe#AP_>fG zQL8h_9PzhWD1OoN7chTfEpDx`f=+V=^yGVKHr&@@)W{bXEgb?w76Q^L{Y`!)07jqsri8|%P8>pkNbFwXMHk2 zZ~K`=d~Sdh?&+bHBZ9<$^E8G$NeWC6D^aR}NX##kfFZ0&pK8qr*Z(uU&lZH^HTmAg zj$DHOeQAzSEM{c)k1Mb-z_-w@Oqp9-S7KGIu>ySucsqFx=${$21ykkq$qD#ogRH7E zR9-yekVhWU9}Do9Ap=yMV#{2h1NN@yj&yrx3&$OZ=z)#khIbVrM2q@lM)@?dQfzX( zJ*j9A_kp@UEuls#=I?O*UFuj6WSULUms8wmSH|<)KuKmg#~tH3DOs0VbtMuzgmL9w2XnkQ+=*cv ztn|h<+67mY zdaM-Jec0`LEGr)uZK+$Y3zfENsJFeBZ-1hT5F;c<#`EGE&&_l)>{)4`k;6TQ>toM% z>^Jg!z!;i^aqko{QPi&O$W_SaTi9!x%H*jZAhdBQ-YHMsjvZUDftOX?pc+}m-JP$Z zPknzGxUPlQ8ZdWka1dSCT51WR6}>mBJW==_(2!JPhgDT%qQ8f$1)gY?sq_`<)d@hQ zwpJ~3YyjF64xm!B^qR0VjNqO~blz`mLCNt~3F*HEW~wAMjklulW)#)DpEmUuVI7o9 z9SfG*@vR(yoSwSEjEnvPL!9;QwqG$K_T7q|sHI&i-&+zCF@`#VONAsqKf>XQY=$UV zg1A=92Jf=<^r5|lu1hZXpXrfx)&C>X%x!A@Q4!hdu(c*qqLUf+Ye;GwNb-GkCFHpx zefwZ&{j!{LHEWYeWI`F8U@;IQ*FVcR&gXnR?NG;o4Kb0*_&x>V7GpjbpU@l$IGNT z=|9R1Pw6p@YidC#^7W($8gmz}t$VajT7%@+w|S7_dRs);h#W)Pm+Yn#ueubq*}zT> zBEb^YH4H!^K|RFpnZOBIYU^RFTIksxiVLykOOj%W^~5dnZVYBPs5%%bpP4(E>U-WF z+c-pMj7IzqdF#|<{R7Gk$}NmgF1UD3pwE(GOJ5R-b+cQ)U8UCTem#xqCMyQsqlc_l>-c59lWc9^@-upAGyA|D`91GnEI9-sX z?&3QZDik-)Qo#b^t*!=z`{JN#zO7Hlb14rKQ9WnZ#?rVULr3Ahtr#Uoau2#Y?r3$F zL1cLR2h{70OZln-ZoRuZ5cPP$5q@7)&pBFR*$D?KU36k0Bzc-D&Y*OGH`C;pq@}95 zCswt-+@^z{1u~U}_`$W!U)5}>Y_yh^E@tG`r&wux4uQiK4_h~i3wflD=v^UJ@y_HW z%0PB(d~=oIg_R8U$GRKaJTntWk6Y#OiNyZTKnT!;X=~2VB{sFMv>1Bx$7^;4*!Te{TCg$2``hA`bY-Q^<(GJMXpH;e)^=PW1(Z(- z008>8@tUTKzR$$k1$G2GOKv^n6m{;Qt_;h7Kj^g@7-xq6gDJ5JdjSr)Q* zIS~$hKo0I6(>y9xQN#>l0J@GXp_6P(!~2Cdr@h4pCm#rX_UGp-^M`%nVUMG3xq`wm znvOI1tn-WVtj`eo%W6$JNbwg@|R%0S| zM#T4Jspt;tagFQAy~K4=uZ+~o>+qgWY_hwn6L;Jwpm|d6Z@Ov(TtWC1+~p#%i)-&W za4hn6s%#Zl@$3Wg&V#6e;(MG>Di+D>uFotnu+bmhb>0rD{%_Y5bw!#nT|1HKzlKZH zn@L6f1td)zbAuRMA2~Cz$ZZkYVfiVai+gbm_2opYy-88b-dT0*=t8eTf&sfXug>-Y z@`LObMdgv^t?3=#9MPAS?98y`0d}e|2*Ov42YOhC-Yrtz~^?fO7IXzDN1txnV$8nR1J8c#gg4Wwl(+{ew;>w|oWsoSo0cIrtS4kYHI3^uHdS~uOBSLIBw zeJwV?59abP{GMi3zZQ(T+Q<2M;I#`+87E91v01O$!K#3`$($349Dv?fzI z1>rLvrCYbFOYiauWBS@KI<~4P`0;K2dN@Hk3BP#8LqTVu)FouAnOG>G!^;@`h4hee z{Sy-8bVkFwz1wErd$IZtZ`|~J4^8f(0sQr;Osxx1iMbWMUG_0xPSfselqvpzePJE< zOtUUR-iq&e(Q=`ts3H77E-mDGwNb$i;ZQ06^}(R2KA(u7!Q}x4E1oWr0pmx2*6>b* zUkaqXCxb$gkEBhBSsCbXa(Mlv#Jlms=>AXgWdo7qZ5%B(Muj zl=ajdKp1O3qD8*s)Sypjs+me|CHlrSaJPh`vD0jQjr=Wx@wEU$jj1<8o^$RT$cu93 z5R%*l{0+jxT>NGBDUv~UIxau?K%COy=FfDELt0x~e6$8ukm~EW;i+h&+$$ad)7vrK zXKuDQ5v6&4K`&@d{*}z+9y&Pa+GbBOU}$`-1qoV&vOg<5@!7s|;F}hY^!>ZHCENpn zkJ^l0NMpe_SQL|S&&2MlvE@v)L12_!9L~XGhnI#B8i>^RbqGSjB%*Fn+sONRjp}m5 z*XQ165n^Tj4xLp`#Ooxnd2Z!4u1%=;RgBmNgwAz!-)|}OaQx&lm2%T^jsFPu;%7~W zC^ZOz5~L78z2v!Mw1^e8xdNuwqiAq}kdR53!~d&76lTnswCcLnLx+pChsSWZc zo$g5-y(hR}bTXk>YTv627fwkR@7N;p%JG!6Z_@AGlOz`)@vK9rnP0YwRuYA6D*kqR zBG1^EY}?L6H3yuTlY-(xX9=aO?s4lerS@HF{MVn4Vo)|sZJ{~AIqY7z#D(`ePhT=L zod#^#w_G{C7uh&3P&tOlPrPv9SG21D(SZ!Z_$c6r00Q;S0UuZ0AqJ`vfo1qk5wec? z{BS$ha634jC#>JD`YE_hR4Hwxtk z(urn%(qq9r=0)vSnEj`Kq~A)5)KWQA)Y&zAnsEP`|6$ z)nORBxuDnI{%}*5QtFTljvLT0`M-g;ji6tnteVlctUX>TI*iswtdV|k+#?PRx+iu9 zc4ADS>L2Z`IF*D+>sEj~SDCwM!{6G06n46a@{lzPW*fdE`7mg&d?70YvD3V4vOVFP zPm|OQPJklM(!-e1oM`@$b0AWfO}gxFkWJ1)apW*hiFCy1*nQM;!9nHEnR*iD`;9#A z42&;WqU)?7Fq>WAxvvELP{zfV`0V6W-A&c~rkEnN!S432w4eAfNoVh1dq!Ku9Z>O( zFnEwRtvwdvHuQ|S&*&`^QngZWcBU{-@UBj>ZjovkGXxIbk1P*I88ME~;tznC&ht3R z1aBOe5Oa=$u*eGHLbzw_p4>jJFz)tdCV-O_Peq#S5xpGOO&OdHLRcS|Uv2eN#=X^L54)@o9|gc6Af+zU@F*SR1FP9$>u zN=X?|2be?%*-)CN%%3_gG-(v_D+PfNK3hzp0Kd*oTYsH}IbKZ;x0iW@&uRv$(+go6 zF3h~l*i=1iIHZ*bNiW6xI1vzb{`mC?QIbt;I1$M9Jd5OCZ{JH`U-L>bP;H{ca$f2e z%;ZLbmqXqfW2FYSen_?8O86}ga;MdY`aOSn#rZ>A%dhL2sGpG;87RQ^o1 zN%_FGv-k^~@Lb-5A2QE#N7uB@Xs_KV^rnH6>0OZ9l~dWc)$Y+O*}C#4Ss8x0QMk2N z?-*Vtno(Gq$Wi0xn!cgc`c(%4x}^u)Il=FW4-#F#W{%J2I7sphiJ8qI6ZT|krZm}-Arxo2nz;qqcvt9iea%~0#Kn1bM(a%f?xi79v4 zD8chsYcrBtIyxqPwre3#Z+~BNm$A&Ub|Uib&B%0%%wXp2=E~ZbMN++yAW&np^7FRb zXPaX)zI!@2k5=Q-4>l{ewuhh_dPiPAPtkXOn^V3Ssrks!NL?ilpjALQfg(jLtTcaK z*y#3P959a|`C2zz@@Cqn@=V3ha^;3H9CDv{I?y-{q{qwx7n=}A7LmNY)Kn#l+U$ms z2{&A8Efe(o^NZiwL`Tm=F8J|5pq}Ba`hV8YZXO6JR(tp^_4?*n(;`>}QrBd$5Oz6# z>rw=3YG0)0kDG0>WfB(92aP|+kZhZhQ%XgJ$>LAxt8kI|&vaoeGY z#_E&~Xcy~L-`jHj;2R=UZ*6y)#QE%rzWKm0Y2POeHV3h!`A<7xi1|cPy8x`7`XxT} zt-JJe3e0;t#LG`M%@QA+G}7DxP-6Y>;kk=97RaenVR|{r$9*PyU4$Ayztx`b3*UiW zg-AFs<+7WoCWr^qX0FUud4zdXwet7WEPLRl%BVIJHwiHne~ZnM#$RH)%!0h5dDc$S}>rRq)i^+V|ALj0mb=u zCYD1wzr>+6!w<{Yic5=SJ-KY{yHL21aimx*1;-?tgi<-Ea(>dRV+p>Mv8%l0 zMX;#%vn#1PCAj5vINra#^^$g=K`nU-2xi~M>NC(_0wRwyX2o<#*;bz0VMBvQ31WBL z=dA2Fj341j8zyxyn#iumnP{(eXXk<3*~+kXEDJ)eKqQ^u14^C66Jku%li<+kc#f@= zxZ`^vF_x$KDE}d$2^ug$$xq);y_s*SS}%2{%*o>7@wNQ`{8TAaW)z*xl5?T~cXqcp z2*(po#T|O9s~Ni%8FIj|V$IC;H_^VrOO##Uwh|-Ho0;fZzs@cYiQ9pp8u0yA6jBEG z@8pQyEAm*0Vu*wMe3nnNw+m~VAyaTiI|0-e|Rt&RH(z8vK?e6>{m3cv!?l7EcM`krWdtbr$7xI9)s}U zoO8e0-JZ2`bX_zln$-g%R%=GZ8@S(A4=6bSPrnbY5{rf|vF{!!k0Mf`TkD=6wY?JB ze{UV^0eKrhR~BrV6P)_BFzo3N;P-Q=wM9bW@=S_c&jb&N zxga$^mY`<}a^Mkz@mhx?OGg+dz8vu{b4il_7x-K7t#*RVe%lQtsJw)088L7ZxR$`4 z?#`Xy~^ut?`yP`5w z!F1hgVd^n!xzbTb%!gs}{uo@RdCNrbG0Ath5Ak$Ot6}Apb_!Nu2r}%0oUsrqfA5hVP0}4v zlA0FkOVGt9Fh{KCO2znziiHQqf5?oLH0Z+CxN>iVMVwz_7ZEQ5zy7FB$30{)C!yD9 zZ{`8#E4YG}+TToGR}|dP@S`*hg*rpo@_G2+hfpWLAX&hMXh_}V-e5ZsD{fb*osJ8V z>>ITRC-6eVL#phl+!xqrq>8y(1fe5}p2MOFW8|IleX%;Cceb#M}S_E7NHARD^#GI_G-_cw?pM~3`rzXH+vp6eoS+#vriz0Log9Z#JOCuxQY$f z`>Wd@n2M_91VubVvO(N@TA35c!6Rg3rpGZXs3o3INE5L=7%X<=`@UyzfjeuJ?s`EK>b0;#mKj`f^ zq?mzklFM8|1EWsVgJm)xJj4CKI_x86V%r*I@jBFauy;Q!%`;8M z)A#ZPVZjn*%W98!3FQv`D*L;?Q4gJE%!LC!!pK(Yr11-{xUrA7ddC_OmA+Jq#{4+( z6cOQ^Efp-zx-XooUFWB)alI#}WJ{uMvY=*X;d*w`F!X`#AeVE!B{yR{NQ(2Sw=>@i zyIl8O#Lo8TB)uOZ>o*@v#%&;d@IdxQVuA$E$CmtcVcKzwG(x9T({Z3+ZMCH;_qc>) zN7&i^#XsEZ8pk4WV&9)<7;|S|D_l8Dm-8o~Tw42FyA}I5l(AuuvV{@&_*OP)!HqEJ z1@HPdm)S4yN_%CK2NcM8ihOe+)gj7SnRrNTiv2$>$G0Wyb@1aEuBaev_9n?Rvku-K z00+m49?wU^QX^aZ6?(W9esnkOX?Yn8k-deEFy>u~JXQ2(`wnmC#dX_fr?Z;`bQ8Bh z`l@|RKk;sP)2ey&u$=AMNb^56C6SaREO%1R5-4zuf*i`eZFCNlbpMx{2Bo@2va?nM z`^`YY^6!^w3fEn{V|r7Q4&uhHDF<5`L^R9B(jzZ=azkZP>AG~2#O&pUi0YJTw5=q; zmS~(!*Vmo$0V)6IQ=o?(gh|L$`Ks&^F&KgyxW5&3+MNQlCY zH+a??(KwAAL?H^UpQq1Q#!x;P32Qk&S)<51$D$?kbJ7O65N{NoU>ZHxot4E@%qHk4 z?j6JZ0%@TFl^GTlJWE`3!YkcZ^yX+vyi3{1(jOEHF`WxO$5LMgnX!Ybm|Ax|n=HAA ztneqL&FbGmB{hu;HZn&Y;r2 z;H<@IRDgqys*yWebfBxylfk`$OULe*Nnh4huk zzu33=i=@k1dY2NjI2*TzL1{J@704L6rV#m!ORh9tZO&4ei!%8VU=g*es`&i?_RBeU z7-&K#5gnQumb`)|sP19rzh@DC7Gbfwi9sJ6+hjh%bIjLW4xCc;1>=&g*CjN53~Jms z{-CY0wy*7TF95|rW2-R*&@S+L%@Y)!M>${43Ym$Ck+@ec9(<5Z)DgTY!}0-Ui=V={ z`4AAI_z}f9cg)06uiDggI6nl_b*vfw>$GU7J+!0l?Z|CG+HP}xSLsJnYId&CFhkS= zmVblTEX&Ab*j>GdTRN~>6~*QL&j0dz9-O9=f+WtnAPVV*%=ZEfF!H#n2l|X5_Tg4) zWE(!CqmM+EV~aK&nyi6p#vR!sY_3kzpTE8)LpT+Z?_BmL2y~o+*ZVpPHyIq5Ajk!p zQJzZ@$)ZSFwCe}{P`@z)%Qw$*SPn<_ zw5IJ7I-17D2!4n#gZz0+`m<<40IpXb61nYO@lDYlGtll&;}Crl?TgRO3}u8 zp0O4P*~t#xvPAO2LuIikv)z+&jrh|QS_-gE`E^ktyAHyjhwr1NL<&LQJpf1XCnz1s zGg#K#x;)d4;N+8y zN6oLFZo3yY#~_Pe}X9udMas!Th5zN1pSY;nA3VX9#D7nsjq@qt5 zu%HV^?#sBO>fAnv=vWH+`pJ$SJ`qwgv7|q2OoBQfchZm25OlIZhM{1{ zW*TLQ&D6u>A>2jgZZdzH6O-q@k<6q#fx^@F-~=iaHq1@#=u-Znv$ret_^V-svd~d; zx5a5(Q5ftja|Zd}OyoY|WeZ46#(oBVh(X9V2Co!$8nrw^q!Hj45n!wd;tCdtA zkepb*O@T_0rJ6{@wF98_7`HXyqg7cgW>)Ed5}-!$M^5;Szk8jf>&*)DlTj$BZprjb zMN>4AF{B6b4ghY>ScG+X{#g4+L{KREM{@kj07aYtjnznhm>Ti`>yC_;Ze3ghl>2z1 zbXDbIni5kiCw-}_v_S%%cVF0N|_8gt;0?>DGieFl=?lFLjPHIWB>iH8FmW$+)>Cp zm1hN$B0Qr4;^R>IWOc$yN1CBU?2Wjf(eQ~`O;@@AJR0FnN$giwu7$-6XIgTFV^2rRG+9&Q#LoR@Q zHDJuW!pvD#ifA&IiTozObWCk`ST|{8)qyZPEr*CEeQ22pvg2@LDVb1j0E49yWIb}0 zcj>3U4<;_N?+Lg;R!aRp6P^|9x!!%LudhTGUGrg;f(Y}Mu4ce^ScU>Xs5xDoj^OTX zlr4Jth#~G-LO%32hD3DIHriLn+B!gT=HqbZsztQgq<1K*Jv#BBB4;PI!K~Cr%zsyR zYThQZiX~A)kM&O}Z*W9YIv@e1Gk;965Vx!R5`zIIKt0r1xNfP}$I`CgV(E}+-D7c+deu`2a?iikKjwt(97}s ze3nX*2w#URN4lBj<|ZF;udCuurt%&d>+?muR7}#dFz!hs2gJ29A?9UqbaHmb48O(Y zG8Sqvf|004Bz_D1r{Xwvo#-WAH3A#}Nx7J>eKdO)d&HJ#dR&!eP$<^RZ40mrpLb5i||VE5`kutY5WX|s=Xko6n&lFXz!lWrD7P!n{vuI&5# zgfe&iWdCQmI?hV(ThrT|3*Um0n8v5$%x_~=C<)M&`llj?R;r78`uk1V@gkyMDODRp z^351?2_*Z)$b8k0h#x!s)qHJWi$c}(^xscz`+*-7T#!z*JmMByXWL)LmHYuE5guO|nG6+S>iHyOlC3&^(%un_3; zp+_~0?DerxDPG4JM zfN&)}`#z|g5zkSw55+RZ5%5+aQl@qzY_KtroW>68Iz?Xn>S*hF)}Mc`CP1vecpQQ( zv%1PkUFbNt^PW|(94ps@q*9wLZy%3HCWx#iesnMqziC59^DITA^pOCN-z%*#en?`4 zEeM7-avqKE1E+=o%a^Wbo11&qW9j!NU0uFNc^076E&S+n5B^ZNYy&`3ztnbXP==qj zN2k6!3)ky9)4e1+fRiH++@iIq#=uI?FPY?(%D8IO6;y@#*`)(tj*)w`$|AKbt{>zR z3i~jCOF+K}BzIXhaVzKtuTNfypkwRkr`NIG}%Bv>TN zBEIroL^@s#ElxN)G^%FQ&eJRei>OK4nSq9=md^zph%RFhiE+hJ+P5wWg|%QQ-vQ~f zT|=rW8NoIdd01{SN$mCTYa`XUs*ZuFdpG0;m6I55`!}WqM%iVdL76BTqg3TrZz|bs zL3n?&ne~QxyL^#o=_Wa|VnU|Lr!=gd|D^bn9mn93S8IJMty|yK*n~0)=7X%oLkPt+ zwf`OwQS^VZLN)JZ)M@4^66d{cqQ49y$-}(@&n@-%IsH_qj!*b;syx1Y-Z2i`GUJo` z-zw{Nihi}K`|ew&>9^TLUAP@&5UMz*?i{cAeyn+0%^6AVlrwd9HP^(IHP^R`dc{Sl zLxL^L7m2re62Q#tR9J+*M6oI!iJ0`j^YE{@f>sLr*S5cDP#~8J6XBxw-Uu&H@RJ}} z-SqEau1G%!7C|(EVMu_gd~=uL_KIn%;BK*JGgd^mj~?|d-X@ApL*IQXCD1p*S2gB) z#h%S&w$>cR9d}V&le-D#-p)&C8J@2S9o#54L>l18Teb(1~;1flc z!w(uY<&*yY<_qPyOm9_ei}lD{r-AT8IXjVU^*aguFk1F_kDnU$!$CX@KK-wHbR=;Q zq1~-J=YKbIq5-vV=qp6jmIQlpv1dQ-g~GKj!&yf+4GN_~EULW!IcMfHL+WsGtxP z>Vu7u=%tBM64L%Ev zYlOzY94+?n<@e~uSyCk{e7^t5nfq?KGhAuJ2Y_%+BBFd-N!Vv~tX2s=yesSCM!nR3 zeKT_iPicgHbh;*krJ#MNeR)&ccC1$YJ7vP9`^zx82{PStI9K$H!yo$}^dDl_@#}m| zs}~JDYOg$|k_cq&VvKroTBFO4+KN>(plVU$d$Jm1RM@Z$Qy9OJb+jpeETb>4<_)d( z!dn=pFqT+w0oSB%b8$C|3uKLil%}u%Z1(dzo~-G4hIWKsWb8=FJQ#gs^V+_Sac?21 zuiNYRbla09F%E~2-0|*6(1gF_DQR7?W^=p)kP56r^Zd?Q!BlcdFz|p$;gDEyOJGD_ zl3;!?g$B_DC7WM+-a{J{X-g;9OSdTk`}FS=ow&4HjxgBz$1S?CDr4gH*UQ(++ptYV zgYdB*xjjnD)E*l%fRH0g)yJZ@8ev_}3&9A&Q%lJC;8>p(DCC4b!0miUbr-?^gl#?^+l3Gux{LAn z(jmZ?iLek2rx|mjT7rc|9?GHDflr1;)~XT4Iiz?2r}%3~*&d8}q&rQWsL}Wv+c#(e z-v)x%o#M*jU7pvNl14>;@iO}AWalbIb))AQFHqZ|>TLf~6}fJKqu!}t2;Zr` zK%@=TUp(3+_>4b~l@z~t&*r6GYH#~pSp&t7VyxTSh1ic_E8b(twlY_sl?Pme)?VqR3zmbToWaZyUsx|`!lCJrt$}5*)jC;r@NQt>bs*)p< zvUNLr#Xl+0vqZ41*g3T8pow~2V=Wb78$D?vFu<>qOCk>^06$sF>)*Rw!?A_Yp(ujk zkQ$K6qtSb9ZF{UDKvj6;@_{JAsiYdais7**cKRf=*YO|>0Gq{Ee0pZ&f(OlES6ADA zX-_+OO6}YFUc9fmenVNq0%!FXkJ2uryi@$wqB^O=fsiFT#YX?|s<;&vJ)c<1yQ0oB z;rFCh0jsq@59abOjbI^{M|WKK=YCk5d2N95 zHR7eBgR61=64-mgi)h&S-M=I~JmvF>0cferU$hh=8Ns);o@>>`s=bwJc{A(E_&=#O z-yKDmvXZ4|;S`&><*6K`CNVk+BQ#Y>Y@X~r=9NETBC#GYU4K69ySZZ)Le(W|3KZA5 z=Qv6!;|^&34g;G$-dq;^Jns@k6^3!ZM}K97yY^-=c9P8DdVJGLUyHvFdMpu=l>Uv0 z;VTs#;7y&COZ~-l%6SBCl~A!5u2@s;?G3dm5&A!}NkOvI2jSxHmW3*`6COE3f8`Q* zS)@-gstVzar3U20M2c8HcV*-=LMf+SIcCqBX287a<{=D%*c$WaMOFJQ&tC86>$fXq*Io~ z4-GxBYcCsl4#9gxF~0oQq8s@qc`XFJXmYj z!@n&<7zETwuYXIY*Q^J~=niNoi8#N@+IfsEI<|mup&|gnYi7NTRfs_GcDZk-OG*d( zsz=FNh&kANUi~q5|64dLk=4qPfdC990g<(hjP=~0uGQtV+@Eyle#nYU7v7l5gbv4e1yuQZ=)ShKx zPZ-q0BuXTiWz-;0YWH(MX_hb@B5vSTf@m~r z{o0%!tvcskt=@q)?&~6Iar!S?L`#=7obyfsMroDDXG|MRr+w&97R2(lBP? zjDn5%>G!E9gqo^5pf+8$hHfduYw z_vO|_s|T=P5pi9P=YfgrSut6$oO|t4UA?Kpv8;Pw+q?i9Zvk{KRcm>l{f|0KTXdI% zdQ16-G5(fe-wc3Ty=g>R6ZEzWq>Sg(a<6Gg(F{w+wG3E-757i6UgY?~8qAPK0*Z<( z&E&8BU4@5fwL6CH&_9U(AvWB*W08|E{bcAUoIkao7$>uOdG+VK2I(?Hz)ugYw*m(C za0QYOBtS$7AboZ)*eZJ8}2QEvdn3LQD`aSA8T)cG}W0SrkQr5ckZ`aDVun)|XAz1EeDy@|SHgIfH8@(3w#?w(N@}I#o z6gp>tAQEc-(MGAT{98l-Zwcsf*5Ww-j@mOQh_cRB<;>B$rH%E6iIe^^hw~jG?Ydj8 zJNK@LsLgo=V8!LpHp-a1*f0RGwagoN;yJRumPWMKB;6y?(LSu586poeK1cwV=lQC# zU6)9w@K%mqx`&*J39_=;Hr{ju7i=g+hJMPLRnmQR%*uwA1DX?SN8W(yMnJewV%xvVkV+!OCqzA8k$F{HfX?6^iaS!F->0@3sZ<3JA#&G)N+=~psJ zoHf;${CgOR$DJei240?-FupPD{h`#H)jksKGn9$}r2k%?PXgrGoUsY# zTz8{Ix{91Jgg~Mp82iy9FA2+EEqt6}`qXY=m?fP#!08|@kwfCr#NH4D|8$xRpR3m% z)D|}zjHn}m%eq?Jpr}x+oH~>v#Vwra$B`v)mPhl6F2(ZV&u*EEKm2Ouag+$&IWQpS z9S2~??Vog3rG**p08fv#7zM_dm*R^U@ru@jMHYjLHw1fcyDzTSts7VXVD*Q04jx6- z-K*b~5p@Y%*yoEm;_WNlJ+9b&;tX0jD^s&wIZOQ?w`eoOLc&V&zNA4U1eD?hAZv4M z*8~O9V*gV|=#zoTU{an`zes`bWMytILd9)RO2e@6f4A z8wgmBnm+t)Lh&{dL46s|Hzqw=w-#e?$y>hPqOjyx#khJ3^>$ku;oqevhWEhMfE@F% zIITnpseq&3njHg;=-2`OEDx7u6ByR_7uY5z5LXt4${rSWhVjj!Wm zP#C-If?<;Uh`XOS?JSUSw^%LGA#M9wQhwfh8OPH(l$18YHxSKheFed&=3Upvf$LU% zQb*9dk43H{Wog=oqyepg`p$-dSjWdmQ(*4tLQuB})nk79l-}WMa;x30nX}I}d#7_a z(G#=o{N&?npmtN#U$U&tA4tC<$WI9e_fcV0=~EnA4F3D(w*GNg@}$73*H7l+<#@zt zh$iqyZ<4_D_`Y2`&8I~kQw0d7=d1RX5RlQ&ulLFT^*jDXWsc+HA;%xS(%4l$HFfSr%)&4daRX~ zMczV&VQmuTmg=FiGRsy&Hez^#->{?(aKDXZ^(@=A>XDWTWnrzxIZ2SCJeU5N4xBAO z@FL^u3VwjU?NBtX>P6AkFWY)%!>r*PMJpJfu< zP%u&iSOGbD#W~}{S=MKz6EU}h8MSS)IcoZF!2Z)OjvvW9J~(_!gP$&7(&1e-=p#*! zl%(kcn`A-X8WlX#{(b`t-+d zBp?d^G%%(nYp;N}k73eUM7ziwCW%R%+M17dQSY*?gzg`^c;~0$*JSt_*U+2jI%%F( zwzP62r9a%T{SiSBGfCuA(?op$+(4M#O0@v^K@~+)L!$pVoB#)^^gH=B+CNe>uk2DNDql-jjbj#qYff@_y zF>o+{s~tnwR@wg>R{a_wke*PCN~s%Vn|_MtIG5~Tk_%+%mg$EgA>3CwEZvF(t7HIc z5ZY|5?nORmSLReaPP0tn^zadxxzo;@Y6I7Gzl?6R2e}VT?pxI=TH` zI1R7CK4a{DBlumIOGmh@txEfy+v+0}MSQcEqb@_o$Ok%B=Aw~rGmL^`?4{uZ?Z#1) z9gDi~_C1?Kx4P0!*m}1S(%>^(nQrAf_jU@X4=?tS(-`-c(JhKxrcpg-l3hWH!cCR* zI{(0+F?v>8Xz>(RsW*A}DzSH4T~Bg0^S9j9Cs@By?t7}B&EE_lFw_5NpR{mH)|R;l z4!aJ1V4mIYEmz)tARni%JZPlXa3Y#E39&qjZE-K_I>?fcP?yTAgkI1`MM-@6&Rp0A zqrU^6!5)N{@IFMpR;7mR`>GFTn7THs6^;MTt0W+;CV?cxC>lS0eOK#QP`yxKgAEm5 zwT0#WJ5y?`Wubl5gJ7%arx&j*tBP<$sPJSiw>$F)Y>WU_({2o=sqH~q0b=={p>Ink z+C*#red*G=f2pCTd|!v+(qJM1+oj@7=L{#)Bz%anyl>d?PT%f2e~ZKp#_}n)vEAZH zhr&Ar5=Ah2XW?Ma2|ol78=bs_lSLha^@c!_6l8HjMJ+-Zjby`dEsB|^a*l25GS~S7?lFsFqn&T!Wa0OMI$1aAGS*x^huL> z!$dYb4-B#wW$~N$V-F#XFU^rc1?>kdXLbIF_v@gWUjah=ER_BVp?fJu8?LY|J-dy! z=&xse6Exo^VR=*p55|eJL==L&Thp?wo4`?cXNIu%7wK2-;U!|Z`}ym zJAK}2n%!{|Op0~F!Q34vcw5lboaww4aIwqe(;aV(^EpINLGO)%6>=#7MqhO{-yycF zS^6NSbh<#uDf*4O2Qqr8(*_K2bt)+W1q!|wcVGXbq@ds*(aTE6DFoqLHv{Q?QMZC; z65xamM;do)W%%Z3-y!aF6?Pb%oE*v4{(2V)Iss1ssRCC0q0`IRE}ELIYoC8nz%+Pm z6kPr~t|)SS$@z=9E)hs3FGzC;X)i4NSDnCV(u!#n5dB78pj;n5{iB?{60G3f7w3U?E+B`wXzu_H)B_$xf+1OpWt1?IT$7J!QhhCy@n||# zfG+hD+KO(PsDdlg*szg&o;G=((-1WxM?pm9KFHPOz74e7h>6rje=pQVyunU;gvdm7iQJ17zz59WWR- z3pHmZ#_oY(R!4OZ7 zm07T5qyv1#ySsZ5S?u|oV>=~UyZ;>)HP04msy(Pt>ulEdp2SJ#AaipA;KUN_P=N)u zlEz6#qhjpg+LuSeXf5k$J(=R}fv!$+t2spscnXyp7ucO9!6^J{nrJ#pmbAci`%OSA zD{kPRy?bl?8=&Q|rX@R|rcqB$-q>byUdP^ph75w^nfSe@CDw}`2v#W-en@!g8RX(FrP;s*2Tw0H s-6qEzr;p}%4#?7w`0Lhv@}CKS5ifM}&~gwRFzBMi5{EpLpWFZd0PmW#MF0Q* literal 0 HcmV?d00001 diff --git a/static/img/blog/sdk-v6/SDK6-light.webp b/static/img/blog/sdk-v6/SDK6-light.webp new file mode 100644 index 0000000000000000000000000000000000000000..90293df91fc27ca7235f6e237923bfb37ce1b818 GIT binary patch literal 65248 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 Date: Mon, 6 Jan 2025 17:05:23 +0100 Subject: [PATCH 15/17] fix: simplify next.js setup --- js-sdk/integrations/react/next/app_router.mdx | 4 +--- js-sdk/integrations/react/next/app_router_next_intl.mdx | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/js-sdk/integrations/react/next/app_router.mdx b/js-sdk/integrations/react/next/app_router.mdx index 1e8a4340a..9caf2154f 100644 --- a/js-sdk/integrations/react/next/app_router.mdx +++ b/js-sdk/integrations/react/next/app_router.mdx @@ -199,14 +199,12 @@ export const { getTolgee, getTranslate, T, getTolgeeStaticInstance } = createServerInstance({ getLocale: getLanguage, createTolgee: async (language) => { - const tolgee = TolgeeBase().init({ + return TolgeeBase().init({ observerOptions: { fullKeyEncode: true, }, language, }); - await tolgee.loadRequired(); - return tolgee; }, }); ``` diff --git a/js-sdk/integrations/react/next/app_router_next_intl.mdx b/js-sdk/integrations/react/next/app_router_next_intl.mdx index a6aa018e1..e3213b52f 100644 --- a/js-sdk/integrations/react/next/app_router_next_intl.mdx +++ b/js-sdk/integrations/react/next/app_router_next_intl.mdx @@ -170,15 +170,12 @@ import { TolgeeBase } from './shared'; export const { getTolgee, getTranslate, T } = createServerInstance({ getLocale: getLocale, createTolgee: async (language) => { - const tolgee = TolgeeBase().init({ + return TolgeeBase().init({ observerOptions: { fullKeyEncode: true, }, language, }); - // preload all the languages for the server instance - await tolgee.loadRequired(); - return tolgee; }, }); ``` From 88bcc5e95b8b803e5914b5354b37a6c5e6c0751a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Mon, 6 Jan 2025 17:19:15 +0100 Subject: [PATCH 16/17] fix: simplify next.js setup --- js-sdk/integrations/react/react_native.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js-sdk/integrations/react/react_native.mdx b/js-sdk/integrations/react/react_native.mdx index d0bef4f86..70e397883 100644 --- a/js-sdk/integrations/react/react_native.mdx +++ b/js-sdk/integrations/react/react_native.mdx @@ -27,7 +27,7 @@ const tolgee = Tolgee() // DevTools will work only for web view .use(DevTools()) .use(FormatSimple()) - // replace with .use(FormatIcu()) for rendering plurals, foramatted numbers, etc. + // replace with .use(FormatIcu()) for rendering plurals, formatted numbers, etc. .init({ language: 'en', From d2a0188c2319f1d7e1e639e54caa5505044f7e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Gran=C3=A1t?= Date: Tue, 7 Jan 2025 09:45:29 +0100 Subject: [PATCH 17/17] feat: remove emojis and add link to next.js docs --- blog/2025-01-06-sdk-v6.mdx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/blog/2025-01-06-sdk-v6.mdx b/blog/2025-01-06-sdk-v6.mdx index 684d0eb8d..26ff4f48e 100644 --- a/blog/2025-01-06-sdk-v6.mdx +++ b/blog/2025-01-06-sdk-v6.mdx @@ -1,7 +1,7 @@ --- slug: sdk-v6 -title: 'Tolgee SDK Version 6 released! 🎉🎉' -description: "Supercharge your localization with Tolgee SDK V6! 🚀 Simplified Next.js integration, smarter translation fetching, and minimal breaking changes for a seamless transition! 🌐" +title: 'Tolgee SDK Version 6 released!' +description: "Supercharge your localization with Tolgee SDK V6! Simplified Next.js integration, smarter translation fetching, and minimal breaking changes for a seamless transition!" image: /img/blog/sdk-v6/SDK6-light.webp authors: [sgranat] tags: ['tolgee', 'sdk', 'release'] @@ -22,7 +22,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; }} /> -With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. 🚀 You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. ✅ +With Tolgee SDK Version 6, fetching translations for your Next.js applications is easier than ever. You can prefetch translation data on the server and seamlessly pass it to the client, ensuring smooth and efficient localization. @@ -38,9 +38,10 @@ const translations = JSON.stringify(await tolgee.loadRequired()); tolgee.addStaticData(JSON.parse(translations)); ``` -This approach ensures the client has the translations ready without additional requests. 🔄✨ +This approach ensures the client has the translations ready without additional requests. +Check the [next.js integration guide](/js-sdk/integrations/react/next/app-router) to see how it's used. -### Advanced Prefetching with `loadMatrix` 🌟 +### Advanced Prefetching with `loadMatrix` For greater control, use `loadMatrix` to prefetch specific languages and namespaces. @@ -62,9 +63,9 @@ await tolgee.loadMatrix({ t('app_title', { ns: 'info' }); ``` -These tools make it simple to fetch and render translations, streamlining the localization process for server-rendered and server components. ✨🎯 +These tools make it simple to fetch and render translations, streamlining the localization process for server-rendered and server components. -## Breaking changes ⚠️ +## Breaking changes Version 6 is just a mild update, so breaking changes are minimal.