feat(extractor): support multi-language in VixSrc

This commit is contained in:
WebStreamr 2025-06-25 19:06:13 +00:00
parent 91d5e9dd36
commit c0fc6ff7ea
No known key found for this signature in database
9 changed files with 95 additions and 16 deletions

View file

@ -8,7 +8,7 @@ import { createTestContext } from '../test';
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const extractorRegistry = new ExtractorRegistry(logger, [new VixSrc(new FetcherMock(`${__dirname}/__fixtures__/VixSrc`))]);
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg' });
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg', de: 'on', en: 'on', it: 'on' });
describe('VixSrc', () => {
test('Full Metal Jacket', async () => {

View file

@ -1,6 +1,12 @@
import bytes from 'bytes';
import { Extractor } from './Extractor';
import { Fetcher, buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy } from '../utils';
import {
buildMediaFlowProxyExtractorStreamUrl,
Fetcher,
getCountryCodes,
iso639FromCountryCode,
supportsMediaFlowProxy,
} from '../utils';
import { Context, CountryCode, Format, UrlResult } from '../types';
export class VixSrc extends Extractor {
@ -29,15 +35,18 @@ export class VixSrc extends Extractor {
const sizeMatch = html.match(/"size":(\d+)/);
const qualityMatch = html.match(/"quality":(\d+)/);
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'VixCloud', url);
const countryCodes = await this.determineCountryCodesFromPlaylist(ctx, playlistUrl);
return [
{
url: await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'VixCloud', url),
url: playlistUrl,
format: Format.hls,
label: this.label,
sourceId: `${this.id}_${countryCode}`,
ttl: this.ttl,
meta: {
countryCodes: [countryCode],
countryCodes,
...(filenameMatch && { title: filenameMatch[1] }),
...(sizeMatch && { bytes: bytes.parse(`${sizeMatch[1]} kb`) as number }),
...(qualityMatch && { height: parseInt(qualityMatch[1] as string) }),
@ -45,4 +54,20 @@ export class VixSrc extends Extractor {
},
];
};
private async determineCountryCodesFromPlaylist(ctx: Context, playlistUrl: URL): Promise<CountryCode[]> {
const playlist = await this.fetcher.text(ctx, playlistUrl);
const countryCodes: CountryCode[] = [CountryCode.it];
getCountryCodes(ctx.config).forEach((countryCode) => {
const iso639 = iso639FromCountryCode(countryCode);
if (!countryCodes.includes(countryCode) && (new RegExp(`#EXT-X-MEDIA:TYPE=AUDIO.*LANGUAGE="${iso639}"`)).test(playlist)) {
countryCodes.push(countryCode);
}
});
return countryCodes;
}
}

View file

@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Italian",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="ita",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Daudio%26rendition%3Dita%26token%3Dgi4-3epkYBCfogktY8L9Tw%26expires%3D1756061649%26edge%3Dsc-u13-01"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="eng",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Daudio%26rendition%3Deng%26token%3Dgi4-3epkYBCfogktY8L9Tw%26expires%3D1756061649%26edge%3Dsc-u13-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Italian",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="ita",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Dsubtitle%26rendition%3D3-ita%26token%3Dgi4-3epkYBCfogktY8L9Tw%26expires%3D1756061649%26edge%3Dsc-u13-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="eng",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Dsubtitle%26rendition%3D4-eng%26token%3Dgi4-3epkYBCfogktY8L9Tw%26expires%3D1756061649%26edge%3Dsc-u13-01"
#EXT-X-STREAM-INF:BANDWIDTH=1200000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=854x480,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Dvideo%26rendition%3D480p%26token%3D7v6ck17PxObPaqCCgb3UfQ%26expires%3D1756061649%26edge%3Dsc-u13-01
#EXT-X-STREAM-INF:BANDWIDTH=2150000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Dvideo%26rendition%3D720p%26token%3Da9W71KeqCbV67VX1xceReQ%26expires%3D1756061649%26edge%3Dsc-u13-01
#EXT-X-STREAM-INF:BANDWIDTH=4500000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftype%3Dvideo%26rendition%3D1080p%26token%3DOOVfUQ_3veZMjPBb3FPkxg%26expires%3D1756061649%26edge%3Dsc-u13-01

View file

@ -0,0 +1,17 @@
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Italian",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="ita",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Daudio%26rendition%3Dita%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="eng",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Daudio%26rendition%3Deng%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English [CC]",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="eng",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D3-eng%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="German",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="ger",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D4-ger%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="French [CC]",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="fre",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D5-fre%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="French",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="fre",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D6-fre%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Italian [Forced]",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="forced-ita",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D7-forced-ita%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Italian",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="ita",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D8-ita%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Japanese",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="jpn",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D9-jpn%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Ukrainian",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="ukr",URI="https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dsubtitle%26rendition%3D10-ukr%26token%3D9Xuf0k_DgmDiASPFeByoWg%26expires%3D1756061649%26edge%3Dsc-u2-01"
#EXT-X-STREAM-INF:BANDWIDTH=1200000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=854x480,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dvideo%26rendition%3D480p%26token%3DgsQVLhHScXTJAxmwkTD2tQ%26expires%3D1756061649%26edge%3Dsc-u2-01
#EXT-X-STREAM-INF:BANDWIDTH=2150000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dvideo%26rendition%3D720p%26token%3Dxqxvb6W5pFM0RE_zkKgMAA%26expires%3D1756061649%26edge%3Dsc-u2-01
#EXT-X-STREAM-INF:BANDWIDTH=4500000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,AUDIO="audio",SUBTITLES="subs"
https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftype%3Dvideo%26rendition%3D1080p%26token%3Dg7Q1oAxS7C07i_q_8_chaA%26expires%3D1756061649%26edge%3Dsc-u2-01

View file

@ -9,6 +9,7 @@ exports[`VixSrc Black Mirror 1`] = `
"bytes": 2389338112,
"countryCodes": [
"it",
"en",
],
"height": 1080,
"title": "Black.Mirror.S04E02.Arkangel.1080p.NF.WEB-DL.ITA.ENG.DDP5.1.H.264.mkv",
@ -29,6 +30,7 @@ exports[`VixSrc Full Metal Jacket 1`] = `
"bytes": 8544718848,
"countryCodes": [
"it",
"en",
],
"height": 1080,
"title": "Full.metal.jacket.1987.1080p.x264.AC3.ITA.AAC.ENG.Subs.mkv",

View file

@ -27,11 +27,9 @@ export enum BlockedReason {
unknown = 'unknown',
}
type NonEmptyArray<T> = [T, ...T[]];
export interface Meta {
bytes?: number;
countryCodes: NonEmptyArray<CountryCode>;
countryCodes: CountryCode[];
height?: number;
title?: string;
}

View file

@ -1,7 +1,13 @@
import { getDefaultConfig } from './config';
import { getCountryCodes, getDefaultConfig } from './config';
describe('getDefaultConfig', () => {
test('has English enabled', () => {
expect(getDefaultConfig().en).toBe('on');
});
});
describe('getCountryCodes', () => {
test('returns only all country codes', () => {
expect(getCountryCodes({ de: 'on', en: 'on', includeExternalUrls: 'on' })).toStrictEqual(['de', 'en']);
});
});

View file

@ -1,7 +1,23 @@
import { Config } from '../types';
import { Config, CountryCode } from '../types';
export const getDefaultConfig = (): Config => {
return { en: 'on' };
};
const isCountryCode = (key: string): key is CountryCode => {
return Object.values(CountryCode).includes(key as CountryCode);
};
export const getCountryCodes = (config: Config): CountryCode[] => {
const countryCodes: CountryCode[] = [];
for (const key in config) {
if (isCountryCode(key)) {
countryCodes.push(key);
}
}
return countryCodes;
};
export const showExternalUrls = (config: Config): boolean => 'includeExternalUrls' in config;

View file

@ -1,12 +1,12 @@
import { CountryCode } from '../types';
const countryCodeMap: Record<CountryCode, { language: string; flag: string }> = {
de: { language: 'German', flag: '🇩🇪' },
en: { language: 'English', flag: '🇺🇸' },
es: { language: 'Castilian Spanish', flag: '🇪🇸' },
fr: { language: 'French', flag: '🇫🇷' },
it: { language: 'Italian', flag: '🇮🇹' },
mx: { language: 'Latin American Spanish', flag: '🇲🇽' },
const countryCodeMap: Record<CountryCode, { language: string; flag: string; iso639: string }> = {
de: { language: 'German', flag: '🇩🇪', iso639: 'ger' },
en: { language: 'English', flag: '🇺🇸', iso639: 'eng' },
es: { language: 'Castilian Spanish', flag: '🇪🇸', iso639: 'spa' },
fr: { language: 'French', flag: '🇫🇷', iso639: 'fra' },
it: { language: 'Italian', flag: '🇮🇹', iso639: 'ita' },
mx: { language: 'Latin American Spanish', flag: '🇲🇽', iso639: 'spa' },
};
export const languageFromCountryCode = (countryCode: CountryCode) => {
@ -16,3 +16,7 @@ export const languageFromCountryCode = (countryCode: CountryCode) => {
export const flagFromCountryCode = (countryCode: CountryCode) => {
return countryCodeMap[countryCode].flag;
};
export const iso639FromCountryCode = (countryCode: CountryCode) => {
return countryCodeMap[countryCode].iso639;
};