@tramvai/module-client-hints 7.2.3 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -225,7 +225,7 @@ const App = () => {
225
225
 
226
226
  Object as a result of parsing user-agent headers or string with [@tinkoff/user-agent](../libs/user-agent.md). Parsing happens only on server-side and parsed info is reused on client-side. If header info is unavailable then takes user-agent and parse it with ua-parser-js lib.
227
227
 
228
- ### Example of usage
228
+ #### Example
229
229
 
230
230
  Here userAgent is used to determine the mobile device.
231
231
 
@@ -260,6 +260,23 @@ export const IS_MOBILE_BANNER = createToken<boolean>('IS_MOBILE_BANNER');
260
260
  export class BannerModule {}
261
261
  ```
262
262
 
263
+ ### `USER_LANGUAGE_TOKEN`:
264
+
265
+ `USER_LANGUAGE_TOKEN` is responsible for determining the user's preferred languages based on this algorithm:
266
+
267
+ - **Server:** Parses `Accept-Language` header from the HTTP request
268
+ - **Client:** Reads `navigator.languages` array
269
+
270
+ Example `Accept-Language` header:
271
+
272
+ ```
273
+ Accept-Language: en-US,en;q=0.9,ru;q=0.8
274
+ ```
275
+
276
+ Parsed to: `['en-US', 'en', 'ru']`
277
+
278
+ The language detection algorithm checks these values against `availableLanguages` to find the best match.
279
+
263
280
  ## Env variables
264
281
 
265
282
  ### TRAMVAI_USER_AGENT_CACHE_MAX
@@ -0,0 +1,19 @@
1
+ import uniq from '@tinkoff/utils/array/uniq';
2
+ import { provide } from '@tramvai/core';
3
+ import { USER_LANGUAGE_TOKEN } from '../tokens.browser.js';
4
+
5
+ const browserUserLanguageProvider = provide({
6
+ provide: USER_LANGUAGE_TOKEN,
7
+ useFactory: () => {
8
+ if (typeof navigator === 'undefined' || !navigator.languages) {
9
+ return ['ru'];
10
+ }
11
+ const languages = Array.from(navigator.languages).map((lang) => {
12
+ // Normalize language codes to ISO-639-1
13
+ return lang.toLowerCase().split('-')[0];
14
+ });
15
+ return uniq(languages);
16
+ },
17
+ });
18
+
19
+ export { browserUserLanguageProvider };
@@ -0,0 +1,4 @@
1
+ export declare const browserUserLanguageProvider: import("@tramvai/core").Provider<unknown, string[] & {
2
+ __type?: "base token" | undefined;
3
+ }>;
4
+ //# sourceMappingURL=language.d.ts.map
package/lib/browser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { declareModule } from '@tramvai/core';
2
2
  import { browserProviders } from './shared/providers.browser.browser.js';
3
- export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN } from './tokens.browser.js';
3
+ export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN, USER_LANGUAGE_TOKEN } from './tokens.browser.js';
4
4
  export { displayMode, fromClientHints, isRetina, isSupposed } from './shared/stores/mediaCheckers.browser.js';
5
5
  export { useDisplayMode, useFromClientHints, useIsRetina, useIsSupposed, useMedia } from './shared/stores/mediaSelectors.browser.js';
6
6
  export { MediaStore, setMedia } from './shared/stores/media.browser.js';
@@ -0,0 +1,8 @@
1
+ export declare const serverUserLanguageProvider: import("@tramvai/core").Provider<{
2
+ requestManager: import("@tramvai/tokens-common").RequestManager & {
3
+ __type?: "base token" | undefined;
4
+ };
5
+ }, string[] & {
6
+ __type?: "base token" | undefined;
7
+ }>;
8
+ //# sourceMappingURL=language.d.ts.map
@@ -0,0 +1,36 @@
1
+ import uniq from '@tinkoff/utils/array/uniq';
2
+ import { provide } from '@tramvai/core';
3
+ import { REQUEST_MANAGER_TOKEN } from '@tramvai/tokens-common';
4
+ import { USER_LANGUAGE_TOKEN } from '../tokens.es.js';
5
+
6
+ /**
7
+ * Parse Accept-Language header into array of language codes, normalized to ISO-639-1
8
+ * Format: "en-US,en;q=0.9,ru;q=0.8" => ["en", "ru"]
9
+ */
10
+ function parseAcceptLanguage(header) {
11
+ return header
12
+ .split(',')
13
+ .filter(Boolean)
14
+ .map((langAndQuality) => {
15
+ const [lang, qualityStr = 'q=1.0'] = langAndQuality.trim().split(';');
16
+ const [_, quality] = qualityStr.split('=');
17
+ return { lang: lang.trim(), quality: parseFloat(quality) };
18
+ })
19
+ .sort((a, b) => b.quality - a.quality)
20
+ .map((item) => item.lang.toLowerCase().split('-')[0]);
21
+ }
22
+ const serverUserLanguageProvider = provide({
23
+ provide: USER_LANGUAGE_TOKEN,
24
+ useFactory: ({ requestManager }) => {
25
+ const acceptLanguage = requestManager.getHeader('accept-language');
26
+ if (typeof acceptLanguage !== 'string') {
27
+ return ['ru'];
28
+ }
29
+ return uniq(parseAcceptLanguage(acceptLanguage));
30
+ },
31
+ deps: {
32
+ requestManager: REQUEST_MANAGER_TOKEN,
33
+ },
34
+ });
35
+
36
+ export { serverUserLanguageProvider };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var uniq = require('@tinkoff/utils/array/uniq');
6
+ var core = require('@tramvai/core');
7
+ var tokensCommon = require('@tramvai/tokens-common');
8
+ var tokens = require('../tokens.js');
9
+
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var uniq__default = /*#__PURE__*/_interopDefaultLegacy(uniq);
13
+
14
+ /**
15
+ * Parse Accept-Language header into array of language codes, normalized to ISO-639-1
16
+ * Format: "en-US,en;q=0.9,ru;q=0.8" => ["en", "ru"]
17
+ */
18
+ function parseAcceptLanguage(header) {
19
+ return header
20
+ .split(',')
21
+ .filter(Boolean)
22
+ .map((langAndQuality) => {
23
+ const [lang, qualityStr = 'q=1.0'] = langAndQuality.trim().split(';');
24
+ const [_, quality] = qualityStr.split('=');
25
+ return { lang: lang.trim(), quality: parseFloat(quality) };
26
+ })
27
+ .sort((a, b) => b.quality - a.quality)
28
+ .map((item) => item.lang.toLowerCase().split('-')[0]);
29
+ }
30
+ const serverUserLanguageProvider = core.provide({
31
+ provide: tokens.USER_LANGUAGE_TOKEN,
32
+ useFactory: ({ requestManager }) => {
33
+ const acceptLanguage = requestManager.getHeader('accept-language');
34
+ if (typeof acceptLanguage !== 'string') {
35
+ return ['ru'];
36
+ }
37
+ return uniq__default["default"](parseAcceptLanguage(acceptLanguage));
38
+ },
39
+ deps: {
40
+ requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
41
+ },
42
+ });
43
+
44
+ exports.serverUserLanguageProvider = serverUserLanguageProvider;
package/lib/server.es.js CHANGED
@@ -2,7 +2,7 @@ import { declareModule, provide } from '@tramvai/core';
2
2
  import { serverProviders } from './shared/providers.server.es.js';
3
3
  import { serverUserAgentProviders } from './server/userAgent.es.js';
4
4
  import { PARSER_CLIENT_HINTS_ENABLED } from './tokens.es.js';
5
- export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN } from './tokens.es.js';
5
+ export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN, USER_LANGUAGE_TOKEN } from './tokens.es.js';
6
6
  export { displayMode, fromClientHints, isRetina, isSupposed } from './shared/stores/mediaCheckers.es.js';
7
7
  export { useDisplayMode, useFromClientHints, useIsRetina, useIsSupposed, useMedia } from './shared/stores/mediaSelectors.es.js';
8
8
  export { MediaStore, setMedia } from './shared/stores/media.es.js';
package/lib/server.js CHANGED
@@ -28,6 +28,7 @@ const ClientHintsModule = /* @__PURE__ */ core.declareModule({
28
28
  exports.PARSER_CLIENT_HINTS_ENABLED = tokens.PARSER_CLIENT_HINTS_ENABLED;
29
29
  exports.USER_AGENT_PARSER_EXTENSIONS_TOKEN = tokens.USER_AGENT_PARSER_EXTENSIONS_TOKEN;
30
30
  exports.USER_AGENT_TOKEN = tokens.USER_AGENT_TOKEN;
31
+ exports.USER_LANGUAGE_TOKEN = tokens.USER_LANGUAGE_TOKEN;
31
32
  exports.displayMode = mediaCheckers.displayMode;
32
33
  exports.fromClientHints = mediaCheckers.fromClientHints;
33
34
  exports.isRetina = mediaCheckers.isRetina;
@@ -3,12 +3,14 @@ import { commandLineListTokens } from '@tramvai/core';
3
3
  import { CONTEXT_TOKEN, STORE_TOKEN } from '@tramvai/tokens-common';
4
4
  import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
5
  import { matchMediaCommand } from '../browser/matchMedia.browser.js';
6
+ import { browserUserLanguageProvider } from '../browser/language.browser.js';
6
7
  import { USER_AGENT_TOKEN } from '../tokens.browser.js';
7
8
  import { UserAgentStore } from './stores/userAgent.browser.js';
8
9
  import { commonProviders } from './providers.browser.js';
9
10
 
10
11
  const browserProviders = [
11
12
  ...commonProviders,
13
+ browserUserLanguageProvider,
12
14
  provide({
13
15
  provide: commandLineListTokens.clear,
14
16
  multi: true,
@@ -3,10 +3,12 @@ import { commandLineListTokens } from '@tramvai/core';
3
3
  import { CONTEXT_TOKEN } from '@tramvai/tokens-common';
4
4
  import { COOKIE_MANAGER_TOKEN } from '@tramvai/tokens-cookie';
5
5
  import { readMediaCommand } from '../server/readMedia.es.js';
6
+ import { serverUserLanguageProvider } from '../server/language.es.js';
6
7
  import { commonProviders } from './providers.es.js';
7
8
 
8
9
  const serverProviders = [
9
10
  ...commonProviders,
11
+ serverUserLanguageProvider,
10
12
  provide({
11
13
  provide: commandLineListTokens.resolveUserDeps,
12
14
  multi: true,
@@ -7,10 +7,12 @@ var core = require('@tramvai/core');
7
7
  var tokensCommon = require('@tramvai/tokens-common');
8
8
  var tokensCookie = require('@tramvai/tokens-cookie');
9
9
  var readMedia = require('../server/readMedia.js');
10
+ var language = require('../server/language.js');
10
11
  var providers = require('./providers.js');
11
12
 
12
13
  const serverProviders = [
13
14
  ...providers.commonProviders,
15
+ language.serverUserLanguageProvider,
14
16
  dippy.provide({
15
17
  provide: core.commandLineListTokens.resolveUserDeps,
16
18
  multi: true,
@@ -3,5 +3,12 @@ import { createToken } from '@tinkoff/dippy';
3
3
  const USER_AGENT_TOKEN = createToken('userAgent');
4
4
  const USER_AGENT_PARSER_EXTENSIONS_TOKEN = createToken('userAgentParserExtensions', { multi: true });
5
5
  const PARSER_CLIENT_HINTS_ENABLED = createToken('client-hints parserClientHints enabled');
6
+ /**
7
+ * Token for user language preferences
8
+ * On server: parsed from Accept-Language header
9
+ * On client: from navigator.languages
10
+ * Returns array of language codes in order of preference and normalized to ISO-639-1
11
+ */
12
+ const USER_LANGUAGE_TOKEN = createToken('user language');
6
13
 
7
- export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN };
14
+ export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN, USER_LANGUAGE_TOKEN };
package/lib/tokens.d.ts CHANGED
@@ -10,4 +10,13 @@ export declare const PARSER_CLIENT_HINTS_ENABLED: (false & {
10
10
  }) | (true & {
11
11
  __type?: "base token" | undefined;
12
12
  });
13
+ /**
14
+ * Token for user language preferences
15
+ * On server: parsed from Accept-Language header
16
+ * On client: from navigator.languages
17
+ * Returns array of language codes in order of preference and normalized to ISO-639-1
18
+ */
19
+ export declare const USER_LANGUAGE_TOKEN: string[] & {
20
+ __type?: "base token" | undefined;
21
+ };
13
22
  //# sourceMappingURL=tokens.d.ts.map
package/lib/tokens.es.js CHANGED
@@ -3,5 +3,12 @@ import { createToken } from '@tinkoff/dippy';
3
3
  const USER_AGENT_TOKEN = createToken('userAgent');
4
4
  const USER_AGENT_PARSER_EXTENSIONS_TOKEN = createToken('userAgentParserExtensions', { multi: true });
5
5
  const PARSER_CLIENT_HINTS_ENABLED = createToken('client-hints parserClientHints enabled');
6
+ /**
7
+ * Token for user language preferences
8
+ * On server: parsed from Accept-Language header
9
+ * On client: from navigator.languages
10
+ * Returns array of language codes in order of preference and normalized to ISO-639-1
11
+ */
12
+ const USER_LANGUAGE_TOKEN = createToken('user language');
6
13
 
7
- export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN };
14
+ export { PARSER_CLIENT_HINTS_ENABLED, USER_AGENT_PARSER_EXTENSIONS_TOKEN, USER_AGENT_TOKEN, USER_LANGUAGE_TOKEN };
package/lib/tokens.js CHANGED
@@ -7,7 +7,15 @@ var dippy = require('@tinkoff/dippy');
7
7
  const USER_AGENT_TOKEN = dippy.createToken('userAgent');
8
8
  const USER_AGENT_PARSER_EXTENSIONS_TOKEN = dippy.createToken('userAgentParserExtensions', { multi: true });
9
9
  const PARSER_CLIENT_HINTS_ENABLED = dippy.createToken('client-hints parserClientHints enabled');
10
+ /**
11
+ * Token for user language preferences
12
+ * On server: parsed from Accept-Language header
13
+ * On client: from navigator.languages
14
+ * Returns array of language codes in order of preference and normalized to ISO-639-1
15
+ */
16
+ const USER_LANGUAGE_TOKEN = dippy.createToken('user language');
10
17
 
11
18
  exports.PARSER_CLIENT_HINTS_ENABLED = PARSER_CLIENT_HINTS_ENABLED;
12
19
  exports.USER_AGENT_PARSER_EXTENSIONS_TOKEN = USER_AGENT_PARSER_EXTENSIONS_TOKEN;
13
20
  exports.USER_AGENT_TOKEN = USER_AGENT_TOKEN;
21
+ exports.USER_LANGUAGE_TOKEN = USER_LANGUAGE_TOKEN;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-client-hints",
3
- "version": "7.2.3",
3
+ "version": "7.3.1",
4
4
  "description": "",
5
5
  "main": "lib/server.js",
6
6
  "module": "lib/server.es.js",
@@ -20,21 +20,21 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@tinkoff/env-validators": "0.6.1",
23
- "@tinkoff/user-agent": "0.9.8",
23
+ "@tinkoff/user-agent": "0.9.10",
24
24
  "@tinkoff/utils": "^2.1.2",
25
- "@tramvai/module-metrics": "7.2.3",
25
+ "@tramvai/module-metrics": "7.3.1",
26
26
  "@tramvai/safe-strings": "0.10.1",
27
- "@tramvai/tokens-child-app": "7.2.3",
28
- "@tramvai/tokens-common": "7.2.3",
29
- "@tramvai/tokens-cookie": "7.2.3",
30
- "@tramvai/tokens-render": "7.2.3",
27
+ "@tramvai/tokens-child-app": "7.3.1",
28
+ "@tramvai/tokens-common": "7.3.1",
29
+ "@tramvai/tokens-cookie": "7.3.1",
30
+ "@tramvai/tokens-render": "7.3.1",
31
31
  "user-agent-data-types": "^0.3.1"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@tinkoff/dippy": "0.13.1",
35
35
  "@tinkoff/logger": "^0.10.516",
36
- "@tramvai/core": "7.2.3",
37
- "@tramvai/state": "7.2.3",
36
+ "@tramvai/core": "7.3.1",
37
+ "@tramvai/state": "7.3.1",
38
38
  "react": ">=16.14.0",
39
39
  "react-dom": ">=16.14.0",
40
40
  "tslib": "^2.4.0"