@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 +13 -0
- package/package.json +1 -1
- package/src/components/composable/useTranslation.ts +64 -39
- package/src/helper/language.ts +47 -28
- package/tests/components/composable/useTranslation.spec.ts +30 -0
- package/tests/components/display/podcasts/PodcastModuleBox.spec.ts +2 -2
- package/tests/components/pages/EmissionPage.spec.ts +1 -0
- package/tests/helper/language.spec.ts +11 -11
- package/vite.config.js +1 -1
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
|
@@ -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
|
|
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
|
-
|
|
97
|
-
|
|
118
|
+
// If user language is native, do not go further
|
|
119
|
+
if (userLanguage === translationData.nativeLanguage) {
|
|
120
|
+
return { ready: userLanguage };
|
|
121
|
+
}
|
|
98
122
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
languages.ready = translationData.nativeLanguage;
|
|
153
|
+
if (avaibility === Availability.ToGenerate && bestAvailable === null) {
|
|
154
|
+
bestAvailable = language;
|
|
130
155
|
}
|
|
131
156
|
}
|
|
132
157
|
|
|
133
|
-
return
|
|
158
|
+
return { ready: bestReady, available: bestAvailable ?? undefined };
|
|
134
159
|
}
|
|
135
160
|
|
|
136
161
|
/**
|
package/src/helper/language.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
+
export function getLanguage(forceToKnown = true): string {
|
|
9
|
+
const nameEQ = "octopus-language=";
|
|
10
|
+
const ca = document.cookie.split(";");
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
31
|
+
expect(wrapper.text()).toContain('1 décembre 2025');
|
|
32
32
|
expect(wrapper.text()).toContain('11:21');
|
|
33
33
|
});
|
|
34
34
|
});
|
|
@@ -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, '
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
])('returns "
|
|
40
|
-
setNavigatorLanguage(
|
|
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