Skip to content

Commit

Permalink
pref(setting-tab): reduce bundle size by removing language-tags dep
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed Feb 17, 2024
1 parent 0b1022c commit 2c9caa0
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 103 deletions.
2 changes: 0 additions & 2 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"@mx/config": "workspace:*",
"@total-typescript/ts-reset": "^0.5.1",
"@types/eslint": "~8.4.6",
"@types/language-tags": "^1.0.4",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"autoprefixer": "^10.4.16",
Expand Down Expand Up @@ -50,7 +49,6 @@
"filenamify": "^6.0.0",
"idb": "^8.0.0",
"iso-639-1": "^3.1.0",
"language-tags": "^1.0.9",
"maverick.js": "0.41.2",
"mime": "^4.0.1",
"monkey-around": "^2.3.0",
Expand Down
13 changes: 13 additions & 0 deletions apps/app/src/lib/group-by.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function groupBy<T, K>(array: T[], getKey: (item: T) => K): Map<K, T[]> {
const map = new Map<K, T[]>();
for (const item of array) {
const key = getKey(item);
const group = map.get(key);
if (group) {
group.push(item);
} else {
map.set(key, [item]);
}
}
return map;
}
74 changes: 74 additions & 0 deletions apps/app/src/lib/lang/lang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/naming-convention */

import iso from "iso-639-1";
import { groupBy } from "../group-by";

export const langExtra = {
"de-AT": "Österreichisches Deutsch",
"de-CH": "Schweizer Hochdeutsch",
"en-AU": "Australian English",
"en-CA": "Canadian English",
"en-GB": "British English",
"en-US": "American English",
"es-ES": "español de España",
"es-MX": "español de México",
"fr-CA": "français canadien",
"fr-CH": "français suisse",
"nl-BE": "Vlaams",
"pt-BR": "português do Brasil",
"pt-PT": "português europeu",
"ro-MD": "moldovenească",
"zh-Hans": "简体中文",
"zh-Hant": "繁體中文",
} as Record<string, string>;

export const getGroupedLangExtra = () =>
groupBy(Object.entries(langExtra), ([k]) => k.split("-")[0]);

export const countryMap = {
"zh-Hans": ["CN", "SG", "MY"],
"zh-Hant": ["TW", "HK", "MO"],
} as Record<string, string[]>;

// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export function langCodeToLabel(code: string): string {
const tags = code.split("-");
const lang = tags[0].toLowerCase();
if (tags.length === 1) {
return iso.getNativeName(lang);
}
const langCountry = tags.slice(0, 2).join("-");
return langExtra[langCountry] || `${iso.getNativeName(tags[0])} (${code})`;
}

function detectChs(subtag: string) {
if (
subtag.toLowerCase() === "hans" ||
countryMap["zh-Hans"].includes(subtag.toUpperCase())
)
return "zh-Hans";
if (
subtag.toLowerCase() === "hant" ||
countryMap["zh-Hant"].includes(subtag.toUpperCase())
)
return "zh-Hant";
return "zh";
}

export function vaildate(code: string) {
const lang = code.split("-")[0].toLowerCase();
return iso.validate(lang);
}

export function format(code: string) {
if (!vaildate(code)) return null;
const tags = code.split("-");
const lang = tags[0].toLowerCase();
if (tags.length === 1) return lang;
const subtag = tags[1];
if (lang === "zh") return detectChs(subtag);
return (
langExtra[`${lang}-${subtag.toUpperCase()}`] ??
`${lang}-${tags.slice(1).join("-")}`
);
}
106 changes: 20 additions & 86 deletions apps/app/src/lib/subtitle.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { readdir, readFile } from "fs/promises";
import { basename, dirname, join } from "path";
import type { TextTrackInit } from "@vidstack/react";
import tag, { type Tag } from "language-tags";
import type { Vault } from "obsidian";
import { Notice, TFile } from "obsidian";
import type { MediaURL } from "@/web/url-match";
import { groupBy } from "./group-by";
import { format, langCodeToLabel } from "./lang/lang";

const supportedFormat = ["vtt", "ass", "ssa", "srt"] as const;
function isCaptionsFile(ext: string): ext is (typeof supportedFormat)[number] {
Expand All @@ -14,67 +15,28 @@ function isCaptionsFile(ext: string): ext is (typeof supportedFormat)[number] {
export function getTracks<F extends FileInfo>(
mediaBasename: string,
siblings: F[],
defaultLang?: string,
defaultLangCode?: string,
): LocalTrack<F>[] {
const subtitles = siblings.flatMap((file) => {
const track = toTrack(file, mediaBasename);
if (!track) return [];
return [track];
});
const defaultLangTag = defaultLang ? tag(defaultLang) : undefined;
const subtitlesByLang = groupBy(subtitles, (v) => v.language);
const allLanguages = [...subtitlesByLang.keys()].map((t) =>
t ? tag(t) : undefined,
);
const subtitleDefaultLang = !defaultLang
const allLanguages = [...subtitlesByLang.keys()];
const subtitleDefaultLang = !defaultLangCode
? allLanguages.filter((l) => !!l)[0]
: allLanguages.find((tag) => {
: allLanguages.find((code) => {
// exact match
if (!tag) return;
return tag.format() === defaultLangTag?.format();
}) ??
allLanguages.find((tag) => {
// script or region code match
if (!tag) return;
const lang = tag.language();
const langDefault = defaultLangTag?.language();
if (!lang || !langDefault || lang.format() !== langDefault.format())
return false;
const script = tag.script(),
scriptDefault = defaultLangTag?.script();
const region = tag.region(),
regionDefault = defaultLangTag?.region();
if (lang.format() === "zh") {
if (scriptDefault?.format() === "Hans") {
return (
script?.format() === "Hans" ||
region?.format() === "CN" ||
region?.format() === "SG"
);
} else if (scriptDefault?.format() === "Hant") {
return (
script?.format() === "Hant" ||
region?.format() === "TW" ||
region?.format() === "HK" ||
region?.format() === "MO"
);
}
}
return (
(script &&
scriptDefault &&
script.format() === scriptDefault.format()) ||
(region &&
regionDefault &&
region.format() === regionDefault.format())
);
if (!code) return;
return code === defaultLangCode;
}) ??
allLanguages.find((tag) => {
allLanguages.find((code) => {
// only language match
if (!tag) return;
const lang = tag.language();
if (!lang) return;
return lang.format() === defaultLangTag!.language()?.format();
if (!code) return;
const lang = code.split("-")[0],
defaultLang = defaultLangCode.split("-")[0];
return lang === defaultLang;
});

const uniqueTracks: LocalTrack<F>[] = [];
Expand All @@ -84,8 +46,7 @@ export function getTracks<F extends FileInfo>(
if (track) {
uniqueTracks.push({
...track,
default:
!!subtitleDefaultLang && lang === subtitleDefaultLang.format(),
default: !!subtitleDefaultLang && lang === subtitleDefaultLang,
});
return;
}
Expand Down Expand Up @@ -173,20 +134,6 @@ export async function getTracksInVault(
);
}

function groupBy<T, K>(array: T[], getKey: (item: T) => K): Map<K, T[]> {
const map = new Map<K, T[]>();
for (const item of array) {
const key = getKey(item);
const group = map.get(key);
if (group) {
group.push(item);
} else {
map.set(key, [item]);
}
}
return map;
}

interface LocalTrack<F extends FileInfo> {
id: string;
kind: "subtitles";
Expand Down Expand Up @@ -224,30 +171,17 @@ function toTrack<F extends FileInfo>(
default: false,
};
}
const [fileBasename, language, ...rest] = file.basename.split(".");
if (fileBasename !== basename || rest.length > 0 || !tag.check(language))
return null;
const [fileBasename, lan, ...rest] = file.basename.split(".");
const langCode = format(lan);
if (fileBasename !== basename || rest.length > 0 || !langCode) return null;

const langTag = tag(language);
return {
kind: "subtitles",
language: langTag.format(),
id: `${file.basename}.${file.extension}.${language}`,
language: langCode,
id: `${file.basename}.${file.extension}.${langCode}`,
src: file,
type: file.extension,
label: langTagToLabel(langTag, file.extension),
label: `${langCodeToLabel(langCode)} (${file.extension})`,
default: false,
};
}

// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export function langTagToLabel(lang: Tag, comment: string) {
const primaryDesc = lang.descriptions()[0];
if (primaryDesc) return `${primaryDesc} (${comment})`;
const langDesc = lang.language()?.descriptions()[0];
const scriptDesc = lang.script()?.descriptions()[0];
const regionDesc = lang.region()?.descriptions()[0];
if (!langDesc) return `${lang.format()} (${comment})`;
if (!scriptDesc && !regionDesc) return langDesc;
return `${langDesc} (${scriptDesc || regionDesc}, ${comment})`;
}
7 changes: 3 additions & 4 deletions apps/app/src/settings/def.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { assertNever } from "assert-never";
import type { Tag } from "language-tags";
import tag from "language-tags";
import type { PaneType } from "obsidian";
import { Notice, Platform, debounce, moment } from "obsidian";
import { createStore } from "zustand";
import { vaildate } from "@/lib/lang/lang";
import { enumerate } from "@/lib/must-include";
import { pick, omit } from "@/lib/pick";
import type { RemoteMediaViewType } from "@/media-view/view-type";
Expand Down Expand Up @@ -120,7 +119,7 @@ export type MxSettings = {
setScreenshotFormat: (
format: "image/png" | "image/jpeg" | "image/webp",
) => void;
setDefaultLanguage: (lang: Tag | null) => void;
setDefaultLanguage: (lang: string | null) => void;
getDefaultLang(): string;
setScreenshotQuality: (quality: number | null) => void;
setTimestampOffset: (offset: number) => void;
Expand Down Expand Up @@ -172,7 +171,7 @@ export function createSettingsStore(plugin: MxPlugin) {
getDefaultLang() {
const userDefaultLang = get().defaultLanguage;
const globalDefaultLang = moment.locale();
if (userDefaultLang && !tag.check(userDefaultLang)) {
if (userDefaultLang && !vaildate(userDefaultLang)) {
new Notice(
`Invalid language code detected in preferences: ${userDefaultLang}, reverting to ${globalDefaultLang}.`,
);
Expand Down
18 changes: 7 additions & 11 deletions apps/app/src/settings/tab.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { pathToFileURL } from "url";
import iso from "iso-639-1";
import tag from "language-tags";
import type { PaneType } from "obsidian";
import {
TextComponent,
Expand All @@ -10,6 +9,7 @@ import {
Menu,
Platform,
} from "obsidian";
import { getGroupedLangExtra } from "@/lib/lang/lang";
import { showAtButton } from "@/lib/menu";
import { LoginModal } from "@/login/modal";
import type MxPlugin from "@/mx-main";
Expand Down Expand Up @@ -326,27 +326,23 @@ export class MxSettingTabs extends PluginSettingTab {
);

const fallback = "_follow_";
const extra = getGroupedLangExtra();
const locales = Object.fromEntries(
iso.getAllCodes().flatMap((code) => {
if (code === "zh") {
return [
["zh-Hans", "zh-Hans (简体中文)"],
["zh-Hant", "zh-Hant (繁體中文)"],
];
}
return [[code, `${code} (${iso.getNativeName(code)})`]];
iso.getAllCodes().flatMap((lang) => {
if (!extra.has(lang)) return [[lang, iso.getNativeName(lang)]];
return [...extra.get(lang)!.values()];
}),
);
new Setting(container)
.setName("Default locale")
.setDesc("The default locale for media subtitles")
.setDesc("The default locale for subtitles")
.addDropdown((dropdown) =>
dropdown
.addOption(fallback, "Follow obsidian locale")
.addOptions(locales)
.setValue(this.state.defaultLanguage ?? fallback)
.onChange((val) =>
this.state.setDefaultLanguage(val === fallback ? null : tag(val)),
this.state.setDefaultLanguage(val === fallback ? null : val),
),
);
}
Expand Down

0 comments on commit 2c9caa0

Please sign in to comment.