fix: dispArtistById prefers Navidrome sortName over kuromoji translation

Added _artistSortMap populated from getArtists (artist listing) and
getArtist (artist detail) responses. dispArtistById now checks the
Navidrome sort tag first, falls back to kuromoji only when missing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-08 22:35:40 +02:00
1 parent da68afe0e0
commit 4c3ec5726c
3 files changed
+21 -6

No files matched your search

+14 -2
View File
@@ -91,7 +91,8 @@ export async function ndAuth() {
// Fetched once per session from the backend, cached in memory.
let _romajiIndex = null;
let _romajiMap = null; // Map<songId, { titleRomaji, artistRomaji, albumRomaji }>
let _artistRomajiMap = null; // Map<artistId, artistRomaji>
let _artistRomajiMap = null; // Map<artistId, artistRomaji> — kuromoji backend
let _artistSortMap = null; // Map<artistId, sortName> — Navidrome sort tag (preferred)
let _albumRomajiMap = null; // Map<albumName(JP), albumRomaji> — for album cards without song ID
let _romajiIndexFetch = null; // in-flight promise dedup
@@ -121,7 +122,16 @@ export async function fetchRomajiIndex() {
return _romajiIndexFetch;
}
export function invalidateRomajiIndex() { _romajiIndex = null; _romajiMap = null; _artistRomajiMap = null; _albumRomajiMap = null; }
export function invalidateRomajiIndex() { _romajiIndex = null; _romajiMap = null; _artistRomajiMap = null; _artistSortMap = null; _albumRomajiMap = null; }
// Populate _artistSortMap from Navidrome artist objects (OpenSubsonic sortName field).
// Called whenever getArtists or getArtist data is loaded so dispArtistById can use it.
export function cacheArtistSortNames(artists) {
if (!_artistSortMap) _artistSortMap = new Map();
for (const a of artists) {
if (a.id && a.sortName && !_isJp(a.sortName)) _artistSortMap.set(String(a.id), a.sortName);
}
}
// ─── ROMAJI OVERRIDES ───
// Stored server-side (shared across all clients). Cached in memory per session.
@@ -164,6 +174,8 @@ export function dispArtistById(id, name) {
if (!S.cfg.showRomaji) return name || '';
const ov = getRomajiOverrides();
if (name && ov[name]) return ov[name];
const sort = _artistSortMap?.get(String(id));
if (sort) return sort;
return _artistRomajiMap?.get(String(id)) || name || '';
}
export function dispAlbumName(name) {
+5 -3
View File
@@ -1,6 +1,6 @@
import { S } from '../state.js';
import { esc, toast } from '../utils.js';
import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, fetchRomajiIndex, dispArtistById } from '../api.js';
import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, fetchRomajiIndex, dispArtistById, cacheArtistSortNames } from '../api.js';
import { makeCard, makeAlbumCard, makeGlobalTrackItem, makeSongItem } from '../ui/components.js';
import { playQueue, dlSongLocal, toggleStar, parseArtists } from '../player/player.js';
import { showCtxMenu, openDlModal } from '../ui/ui.js';
@@ -99,7 +99,9 @@ export async function loadArtistDetail(artist) {
// We reveal the hero with the album cover immediately, then patch in the artist photo async.
const [albumRes] = await Promise.allSettled([albumP]);
const _firstAlbums = albumRes.status === 'fulfilled' ? (albumRes.value?.artist?.album || []) : [];
const _ndArtist = albumRes.status === 'fulfilled' ? albumRes.value?.artist : null;
if (_ndArtist) cacheArtistSortNames([_ndArtist]);
const _firstAlbums = _ndArtist?.album || [];
const _bgCover = _firstAlbums[0] ? coverUrl(_firstAlbums[0].coverArt, 512) : null;
const _revealHero = () => requestAnimationFrame(() => {
@@ -171,7 +173,7 @@ export async function loadArtistDetail(artist) {
}
// ── Top Songs ──
const ndAlbums = albumRes.status === 'fulfilled' ? (albumRes.value?.artist?.album || []) : [];
const ndAlbums = _ndArtist?.album || [];
const topSongs = topRes.status === 'fulfilled' ? (topRes.value?.topSongs?.song || []) : [];
const iTracks = iTracksRes.status === 'fulfilled' ? (iTracksRes.value || []).filter(t => t.wrapperType === 'track') : [];
const iAlbums = iAlbumsRes.status === 'fulfilled' ? (iAlbumsRes.value || []) : [];
+2 -1
View File
@@ -1,6 +1,6 @@
import { S } from '../state.js';
import { esc } from '../utils.js';
import { api, apiCached, apiIsCached, coverUrl, fetchRomajiIndex, dispArtistById } from '../api.js';
import { api, apiCached, apiIsCached, coverUrl, fetchRomajiIndex, dispArtistById, cacheArtistSortNames } from '../api.js';
import { makeCard, makeAlbumCard, makeSongItem } from '../ui/components.js';
import { playQueue } from '../player/player.js';
// Circular — safe (called inside event handlers)
@@ -202,6 +202,7 @@ export async function loadArtists() {
try {
const r = await apiCached('getArtists');
const all = (r.artists?.index || []).flatMap(i => i.artist || []);
cacheArtistSortNames(all);
if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {});
g.innerHTML = '';
all.forEach(a => {