@saooti/octopus-sdk 41.9.1 → 41.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.9.2 (07/04/2026)
4
+
5
+ **Features**
6
+
7
+ - **12535** - Amélioration de la sélection de la langue utilisée
8
+ - Prend maintenant en compte les langues alternatives du navigateur
9
+ - Prend en compte les langue "de base" des variantes (par exemple "fr" sera
10
+ utilisé pour un utilisateur "fr-CH")
11
+
12
+ **Fixes**
13
+
14
+ - Correction tests
15
+
3
16
  ## 41.9.1 (07/04/2026)
4
17
 
5
18
  **Features**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.9.1",
3
+ "version": "41.9.2",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -14,6 +14,12 @@ interface RelevantLanguages {
14
14
  available?: string;
15
15
  }
16
16
 
17
+ enum Availability {
18
+ Available,
19
+ ToGenerate,
20
+ Unavailable
21
+ }
22
+
17
23
  export const useTranslation = () => {
18
24
 
19
25
  const authStore = useAuthStore();
@@ -72,6 +78,32 @@ export const useTranslation = () => {
72
78
  return getConfigurationFor(language, emissionTranslation, orgTranslation);
73
79
  }
74
80
 
81
+ function getLanguageAvailability(language: string, emission: Emission, translationData: PodcastTranslationData): Availability {
82
+
83
+ // 1. If language of podcast == language of browser, use that language
84
+ if (language === translationData.nativeLanguage) {
85
+ return Availability.Available;
86
+ } else {
87
+ const langConfig = getTranslationConfig(language, emission);
88
+
89
+ // 2. If the language of the browser is available, use it
90
+ if (langConfig === CreateTranslation.ALWAYS) {
91
+ return Availability.Available;
92
+ }
93
+ // 2b. If the language is on demand, check if generated
94
+ else if (langConfig === CreateTranslation.ON_DEMAND) {
95
+ // Check if translation is already generated
96
+ if (translationData.translations.find(t => t.language === language && t.state === TranslationState.FINISHED)) {
97
+ return Availability.Available;
98
+ } else {
99
+ return Availability.ToGenerate;
100
+ }
101
+ }
102
+ }
103
+
104
+ return Availability.Unavailable;
105
+ }
106
+
75
107
  /**
76
108
  * Get the relevant language for the current user
77
109
  * @param translationData The translation data for the podcast
@@ -81,56 +113,49 @@ export const useTranslation = () => {
81
113
  * undefined if *ready* is better)
82
114
  */
83
115
  async function getMostRelevantLanguage(translationData: PodcastTranslationData): Promise<RelevantLanguages> {
84
- const languages: RelevantLanguages = {
85
- ready: translationData.nativeLanguage
86
- };
87
- const baseLanguage = getLanguage();
88
-
89
- // 1. If language of podcast == language of browser, use that language
90
- if (baseLanguage === translationData.nativeLanguage) {
91
- languages.ready = baseLanguage;
92
- } else {
93
- const podcast = await podcastApi.get(translationData.podcastId);
94
- const emission = podcast.emission;
116
+ const userLanguage = getLanguage(false);
95
117
 
96
- const baseLangConfig = getTranslationConfig(baseLanguage, emission);
97
- const defaultLangConfig = getTranslationConfig(DEFAULT_LANGUAGE, emission);
118
+ // If user language is native, do not go further
119
+ if (userLanguage === translationData.nativeLanguage) {
120
+ return { ready: userLanguage };
121
+ }
98
122
 
99
- // 2. If the language of the browser is available, use it
100
- if (baseLangConfig === CreateTranslation.ALWAYS) {
101
- languages.ready = baseLanguage;
123
+ // Compute all available languages. Use language set on site, then
124
+ // languages defined on browser, then default, and finally native language
125
+ const languages: string[] = [];
126
+ [userLanguage, ...navigator.languages, DEFAULT_LANGUAGE, translationData.nativeLanguage].forEach(l => {
127
+ if (!languages.includes(l)) {
128
+ languages.push(l);
102
129
  }
103
- // 2b. If the language is on demand, check if generated
104
- else if (baseLangConfig === CreateTranslation.ON_DEMAND && !languages.available) {
105
- // Check if translation is already generated
106
- if (translationData.translations.find(t => t.language === baseLanguage && t.state === TranslationState.FINISHED)) {
107
- languages.ready = baseLanguage;
108
- } else {
109
- languages.available = baseLanguage;
130
+ // For each language, if it is a variant (for example fr-CH), also
131
+ // add the base language
132
+ if (l.includes('-')) {
133
+ const base = l.split('-')[0];
134
+ if (!languages.includes(base)) {
135
+ languages.push(base);
110
136
  }
111
137
  }
138
+ });
112
139
 
113
- // 3. If default language is available, use it
114
- else if (defaultLangConfig === CreateTranslation.ALWAYS) {
115
- languages.ready = DEFAULT_LANGUAGE;
116
- }
117
- // 3b. If the language is on demand, it will be created
118
- else if (defaultLangConfig === CreateTranslation.ON_DEMAND && !languages.available) {
119
- // Check if translation is already generated
120
- if (translationData.translations.find(t => t.language === DEFAULT_LANGUAGE && t.state === TranslationState.FINISHED)) {
121
- languages.ready = DEFAULT_LANGUAGE;
122
- } else {
123
- languages.available = DEFAULT_LANGUAGE;
124
- }
140
+ const podcast = await podcastApi.get(translationData.podcastId);
141
+ const emission = podcast.emission;
142
+
143
+ let bestReady: string|null = null;
144
+ let bestAvailable: string|null = null;
145
+
146
+ for (const language of languages) {
147
+ const avaibility = getLanguageAvailability(language, emission, translationData);
148
+ if (avaibility === Availability.Available && bestReady === null) {
149
+ bestReady = language;
150
+ break;
125
151
  }
126
152
 
127
- // 4. Otherwise, use default language of podcast
128
- else {
129
- languages.ready = translationData.nativeLanguage;
153
+ if (avaibility === Availability.ToGenerate && bestAvailable === null) {
154
+ bestAvailable = language;
130
155
  }
131
156
  }
132
157
 
133
- return languages;
158
+ return { ready: bestReady, available: bestAvailable ?? undefined };
134
159
  }
135
160
 
136
161
  /**
@@ -1,38 +1,57 @@
1
1
  /**
2
2
  * Utility function to retrieve most relevant language for the user
3
+ * @params forceToKnown When set to true (default), will limit language to one
4
+ * one of 6 known languages. When set to false, allow all
5
+ * languages.
3
6
  * @returns The most relevant language
4
7
  */
5
- export function getLanguage(): string {
6
- const nameEQ = "octopus-language=";
7
- const ca = document.cookie.split(";");
8
+ export function getLanguage(forceToKnown = true): string {
9
+ const nameEQ = "octopus-language=";
10
+ const ca = document.cookie.split(";");
8
11
 
9
- let language = "";
10
- for (const valueCookie of ca) {
11
- let c = valueCookie;
12
- while (c.startsWith(" ")) {
13
- c = c.substring(1, c.length);
12
+ let language = "";
13
+ for (const valueCookie of ca) {
14
+ let c = valueCookie;
15
+ while (c.startsWith(" ")) {
16
+ c = c.substring(1, c.length);
17
+ }
18
+ if (0 === c.indexOf(nameEQ)) {
19
+ language = c.substring(nameEQ.length, c.length);
20
+ break;
21
+ }
14
22
  }
15
- if (0 === c.indexOf(nameEQ)) {
16
- language = c.substring(nameEQ.length, c.length);
17
- break;
18
- }
19
- }
20
23
 
21
- if (0 === language.length) {
22
- const navigatorLang = navigator.language;
23
- language = "fr";
24
- if (navigatorLang.includes("en")) {
25
- language = "en";
26
- } else if (navigatorLang.includes("it")) {
27
- language = "it";
28
- } else if (navigatorLang.includes("sl")) {
29
- language = "sl";
30
- } else if (navigatorLang.includes("es")) {
31
- language = "es";
32
- } else if (navigatorLang.includes("de")) {
33
- language = "de";
24
+ if (0 === language.length) {
25
+ if (forceToKnown === true) {
26
+ // Choose best avilable language
27
+ for (const navigatorLang of navigator.languages) {
28
+ if (navigatorLang.includes("fr")) {
29
+ language = "fr";
30
+ break;
31
+ } else if (navigatorLang.includes("en")) {
32
+ language = "en";
33
+ break;
34
+ } else if (navigatorLang.includes("it")) {
35
+ language = "it";
36
+ break;
37
+ } else if (navigatorLang.includes("sl")) {
38
+ language = "sl";
39
+ break;
40
+ } else if (navigatorLang.includes("es")) {
41
+ language = "es";
42
+ break;
43
+ } else if (navigatorLang.includes("de")) {
44
+ language = "de";
45
+ break;
46
+ }
47
+ }
48
+ if (!language) {
49
+ language = "fr";
50
+ }
51
+ } else {
52
+ language = navigator.language;
53
+ }
34
54
  }
35
- }
36
55
 
37
- return language;
56
+ return language;
38
57
  }
@@ -76,6 +76,36 @@ describe('useTranslation', () => {
76
76
  });
77
77
  });
78
78
 
79
+ describe('getMostRelevantLanguage', () => {
80
+ it('bases its results on the language alternatives in the browser', async () => {
81
+ vi.mocked(getLanguage).mockReturnValue('de');
82
+
83
+ Object.defineProperty(navigator, 'languages', {
84
+ value: ['fr', 'it', 'de'],
85
+ configurable: true
86
+ });
87
+
88
+ const translationData = { nativeLanguage: 'it', podcastId: 0, translations: [] };
89
+
90
+ const result = await composable.getMostRelevantLanguage(translationData)
91
+ expect(result.ready).toBe('it');
92
+ });
93
+
94
+ it('takes into consideration the "base" languages of variants', async () => {
95
+ vi.mocked(getLanguage).mockReturnValue('fr-CH');
96
+
97
+ Object.defineProperty(navigator, 'languages', {
98
+ value: ['fr-CH', 'it', 'de'],
99
+ configurable: true
100
+ });
101
+
102
+ const translationData = { nativeLanguage: 'fr', podcastId: 0, translations: [] };
103
+
104
+ const result = await composable.getMostRelevantLanguage(translationData)
105
+ expect(result.ready).toBe('fr');
106
+ });
107
+ });
108
+
79
109
  describe('getMostRelevantTranslation', () => {
80
110
  it('uses the native language when it matches the browser language', async () => {
81
111
  await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' }));
@@ -21,14 +21,14 @@ describe('PodcastModuleBox', () => {
21
21
 
22
22
  it('shows the date without time by default', async() => {
23
23
  const wrapper = await mount(podcast);
24
- expect(wrapper.text()).toContain('1 December 2025');
24
+ expect(wrapper.text()).toContain('1 décembre 2025');
25
25
  expect(wrapper.text()).not.toContain('11:21');
26
26
  });
27
27
 
28
28
  it('shows the date with time when enabled in SdkParams', async() => {
29
29
  initialize({ generalParameters: { showTimeWithDates: true } });
30
30
  const wrapper = await mount(podcast);
31
- expect(wrapper.text()).toContain('1 December 2025');
31
+ expect(wrapper.text()).toContain('1 décembre 2025');
32
32
  expect(wrapper.text()).toContain('11:21');
33
33
  });
34
34
  });
@@ -33,6 +33,7 @@ function makeReadyPodcast(seasonMode: SeasonMode = SeasonMode.NO_SEASON) {
33
33
  podcast.seasonNumber = 1;
34
34
  podcast.seasonEpisodeNumber = 3;
35
35
  podcast.emission.seasonMode = seasonMode;
36
+ podcast.valid = true;
36
37
  return podcast;
37
38
  }
38
39
 
@@ -2,8 +2,8 @@ import { describe, it, expect, afterEach } from 'vitest';
2
2
  import { getLanguage } from '../../src/helper/language';
3
3
 
4
4
  function setNavigatorLanguage(lang: string) {
5
- Object.defineProperty(navigator, 'language', {
6
- value: lang,
5
+ Object.defineProperty(navigator, 'languages', {
6
+ value: [lang],
7
7
  configurable: true,
8
8
  });
9
9
  }
@@ -29,15 +29,15 @@ describe('language', () => {
29
29
 
30
30
  describe('navigator-based detection', () => {
31
31
  it.each([
32
- ['en-US', 'en'],
33
- ['it-IT', 'it'],
34
- ['sl-SI', 'sl'],
35
- ['de-DE', 'de'],
36
- ['es-ES', 'es'],
37
- ['fr-FR', 'fr'],
38
- ['ja-JP', 'fr'],
39
- ])('returns "%s" for navigator language %s', (navigatorLang, expected) => {
40
- setNavigatorLanguage(navigatorLang);
32
+ { input: 'en-US', expected: 'en' },
33
+ { input: 'it-IT', expected: 'it' },
34
+ { input: 'sl-SI', expected: 'sl' },
35
+ { input: 'de-DE', expected: 'de' },
36
+ { input: 'es-ES', expected: 'es' },
37
+ { input: 'fr-FR', expected: 'fr' },
38
+ { input: 'ja-JP', expected: 'fr' }
39
+ ])('returns "$expected" for navigator language $input', ({ input , expected }) => {
40
+ setNavigatorLanguage(input);
41
41
  expect(getLanguage()).toBe(expected);
42
42
  });
43
43
  });
package/vite.config.js CHANGED
@@ -73,4 +73,4 @@ export default defineConfig(({ mode }) => {
73
73
  }
74
74
  });
75
75
 
76
- /* eslint-enable */
76
+ /* eslint-enable */