feat: 全站20个模板添加国际化(i18n)支持 - 简体中文/繁体中文/英文三语切换

- 所有18套前端模板 + 管理后台共20个HTML文件添加i18n引擎
- 前端JavaScript方案:I18N_LOCALES翻译字典 + data-i18n属性 + t()函数调用
- 语言切换器UI:下拉菜单选择,匹配各模板主题风格
- 语言持久化:localStorage存储 + URL参数覆盖 + 浏览器语言自动检测
- onLangChange回调:语言切换时动态重新渲染所有内容
- 支持语言:zh-CN(简体中文) / zh-TW(繁体中文) / en(English)
This commit is contained in:
2026-09-09 16:58:40 +08:00
parent 69c59f4fbd
commit a84129330e
20 changed files with 9343 additions and 3855 deletions

View File

@@ -133,6 +133,33 @@ header{
.nav-icon-btn:hover{background:var(--glass-strong);transform:scale(1.05)}
.nav-icon-btn svg{width:20px;height:20px;fill:#fff}
/* Language switcher - gradient theme */
.lang-switcher{position:relative;flex-shrink:0}
.lang-btn{
display:flex;align-items:center;gap:6px;padding:7px 14px;border-radius:20px;
font-size:13px;font-weight:600;color:var(--text-soft);cursor:pointer;
background:var(--glass);border:1px solid var(--glass-border);
transition:all .3s;
}
.lang-btn:hover{color:#fff;background:var(--glass-strong);border-color:rgba(255,255,255,0.35)}
.lang-btn .ico{font-size:15px}
.lang-dropdown{
position:absolute;top:calc(100% + 8px);right:0;min-width:150px;z-index:1001;
background:rgba(20,16,50,0.85);backdrop-filter:blur(20px);border:1px solid var(--glass-border);
border-radius:var(--radius);padding:6px;
box-shadow:0 12px 40px rgba(0,0,0,0.5);
opacity:0;visibility:hidden;transform:translateY(-8px);transition:.25s;
}
.lang-dropdown.open{opacity:1;visibility:visible;transform:translateY(0)}
.lang-option{
display:flex;align-items:center;gap:10px;padding:9px 14px;border-radius:10px;
font-size:13px;color:var(--text-soft);cursor:pointer;transition:all .2s;
}
.lang-option:hover{background:rgba(108,92,231,0.15);color:#fff}
.lang-option.active{color:#fff;font-weight:700}
.lang-option .check{font-size:14px;opacity:0;transition:opacity .2s}
.lang-option.active .check{opacity:1}
/* ============ Hero ============ */
.hero{
position:relative;margin:28px auto;border-radius:var(--radius-lg);
@@ -477,16 +504,33 @@ footer{
<div class="nav-inner">
<div class="logo"><span class="dot"></span>乐流 <span class="grad-text">Music</span></div>
<nav class="nav-links">
<a class="active" data-nav="home">首页</a>
<a data-nav="explore">探索</a>
<a data-nav="hot">热门</a>
<a data-nav="new">新歌</a>
<a class="active" data-nav="home" data-i18n="nav_home">首页</a>
<a data-nav="explore" data-i18n="nav_explore">探索</a>
<a data-nav="hot" data-i18n="nav_hot">热门</a>
<a data-nav="new" data-i18n="nav_new">新歌</a>
</nav>
<div class="nav-search">
<span class="icon"><svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z"/></svg></span>
<input id="navSearchInput" type="text" placeholder="搜索歌曲、歌手、专辑…">
<input id="navSearchInput" type="text" data-i18n-placeholder="search_placeholder" placeholder="搜索歌曲、歌手、专辑…">
</div>
<button class="nav-icon-btn" title="我的喜欢" id="navLikeBtn">
<div class="lang-switcher" id="langSwitcher">
<button class="lang-btn" id="langBtn" onclick="toggleLangDropdown()">
<span class="ico">&#127760;</span>
<span id="langLabel">中文</span>
</button>
<div class="lang-dropdown" id="langDropdown">
<div class="lang-option" data-lang="zh-CN" onclick="switchLang('zh-CN')">
<span class="check">&#10003;</span><span>简体中文</span>
</div>
<div class="lang-option" data-lang="zh-TW" onclick="switchLang('zh-TW')">
<span class="check">&#10003;</span><span>繁體中文</span>
</div>
<div class="lang-option" data-lang="en" onclick="switchLang('en')">
<span class="check">&#10003;</span><span>English</span>
</div>
</div>
</div>
<button class="nav-icon-btn" data-i18n-title="nav_like_title" title="我的喜欢" id="navLikeBtn">
<svg viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
</button>
</div>
@@ -497,24 +541,24 @@ footer{
<div class="hero-shape s1"></div>
<div class="hero-shape s2"></div>
<div class="hero-shape s3"></div>
<h1>音乐无界,<span class="grad-text">流光溢彩</span></h1>
<p>沉浸于色彩的律动,让每一个音符都化作光影。千万曲库,一键聆听,开启你的专属音乐宇宙。</p>
<h1 data-i18n="hero_title">音乐无界,<span class="grad-text">流光溢彩</span></h1>
<p data-i18n="hero_subtitle">沉浸于色彩的律动,让每一个音符都化作光影。千万曲库,一键聆听,开启你的专属音乐宇宙。</p>
<div class="actions">
<button class="btn-grad" id="heroListenBtn">立即聆听</button>
<button class="btn-ghost" id="heroExploreBtn">浏览曲库</button>
<button class="btn-grad" id="heroListenBtn" data-i18n="hero_listen">立即聆听</button>
<button class="btn-ghost" id="heroExploreBtn" data-i18n="hero_browse">浏览曲库</button>
</div>
<div class="hero-stats">
<div class="st"><b class="grad-text">1280万+</b><span>在线曲库</span></div>
<div class="st"><b class="grad-text">320K</b><span>无损音质</span></div>
<div class="st"><b class="grad-text">每日更新</b><span>新歌首发</span></div>
<div class="st"><b class="grad-text">1280万+</b><span data-i18n="stat_songs">在线曲库</span></div>
<div class="st"><b class="grad-text">320K</b><span data-i18n="stat_quality">无损音质</span></div>
<div class="st"><b class="grad-text" data-i18n="stat_daily_val">每日更新</b><span data-i18n="stat_daily">新歌首发</span></div>
</div>
</section>
<!-- 分类筛选 -->
<section class="container reveal" id="secCat" style="padding-top:8px">
<div class="sec-head">
<div class="sec-title"><span class="bar"></span>音乐分类</div>
<span class="sec-sub">查看全部 ›</span>
<div class="sec-title"><span class="bar"></span><span data-i18n="section_genres">音乐分类</span></div>
<span class="sec-sub" data-i18n="see_all">查看全部 ›</span>
</div>
<div class="cat-bar" id="catBar">
<div class="cat-pill skeleton" style="width:80px;height:38px;border-radius:22px"></div>
@@ -527,8 +571,8 @@ footer{
<!-- 热门榜单 -->
<section class="container reveal" id="secHot">
<div class="sec-head">
<div class="sec-title"><span class="bar"></span>热门榜单</div>
<span class="sec-sub" data-scroll="plaza">查看全部 ›</span>
<div class="sec-title"><span class="bar"></span><span data-i18n="section_hot">热门榜单</span></div>
<span class="sec-sub" data-scroll="plaza" data-i18n="see_all">查看全部 ›</span>
</div>
<div class="hot-grid" id="hotGrid">
<div class="sk-card"><div class="sk-cover skeleton" style="aspect-ratio:1.4"></div></div>
@@ -546,8 +590,8 @@ footer{
<!-- 新歌首发 -->
<section class="container reveal" id="secNew">
<div class="sec-head">
<div class="sec-title"><span class="bar"></span>新歌首发</div>
<span class="sec-sub" data-scroll="plaza">更多新歌 ›</span>
<div class="sec-title"><span class="bar"></span><span data-i18n="section_new">新歌首发</span></div>
<span class="sec-sub" data-scroll="plaza" data-i18n="more_new">更多新歌 ›</span>
</div>
<div class="new-scroll" id="newScroll">
<div class="new-card"><div class="nc-cover skeleton"></div></div>
@@ -560,19 +604,19 @@ footer{
<!-- 音乐广场 -->
<section class="container reveal" id="plaza">
<div class="sec-head">
<div class="sec-title"><span class="bar"></span>音乐广场</div>
<div class="sec-title"><span class="bar"></span><span data-i18n="section_plaza">音乐广场</span></div>
<span class="sec-sub" id="plazaCount">—</span>
</div>
<div class="plaza-toolbar">
<div class="sort-group" id="sortGroup">
<div class="sort-btn active" data-sort="">默认</div>
<div class="sort-btn" data-sort="plays">播放最多</div>
<div class="sort-btn" data-sort="likes">收藏最多</div>
<div class="sort-btn" data-sort="newest">最新发布</div>
<div class="sort-btn active" data-sort="" data-i18n="sort_default">默认</div>
<div class="sort-btn" data-sort="plays" data-i18n="sort_plays">播放最多</div>
<div class="sort-btn" data-sort="likes" data-i18n="sort_likes">收藏最多</div>
<div class="sort-btn" data-sort="newest" data-i18n="sort_newest">最新发布</div>
</div>
<div class="plaza-search">
<div class="wrap">
<input id="plazaSearchInput" type="text" placeholder="搜索歌曲…">
<input id="plazaSearchInput" type="text" data-i18n-placeholder="plaza_search_placeholder" placeholder="搜索歌曲…">
</div>
</div>
</div>
@@ -585,22 +629,22 @@ footer{
<div class="container foot-inner">
<div class="foot-col foot-brand">
<div class="logo"><span class="dot"></span>乐流 <span class="grad-text">Music</span></div>
<p>音乐无界,流光溢彩。用色彩诠释旋律,用科技传递感动。</p>
<p data-i18n="footer_desc">音乐无界,流光溢彩。用色彩诠释旋律,用科技传递感动。</p>
</div>
<div class="foot-col">
<h4>产品</h4>
<a>在线播放</a><a>个性推荐</a><a>歌单广场</a><a>无损音质</a>
<h4 data-i18n="footer_product">产品</h4>
<a data-i18n="footer_play">在线播放</a><a data-i18n="footer_recommend">个性推荐</a><a data-i18n="footer_playlist">歌单广场</a><a data-i18n="footer_lossless">无损音质</a>
</div>
<div class="foot-col">
<h4>发现</h4>
<a>热门榜单</a><a>新歌首发</a><a>分类浏览</a><a>歌手专区</a>
<h4 data-i18n="footer_discover">发现</h4>
<a data-i18n="nav_hot">热门榜单</a><a data-i18n="section_new">新歌首发</a><a data-i18n="section_genres">分类浏览</a><a data-i18n="footer_artist">歌手专区</a>
</div>
<div class="foot-col">
<h4>关于</h4>
<a>关于我们</a><a>用户协议</a><a>隐私政策</a><a>联系合作</a>
<h4 data-i18n="footer_about">关于</h4>
<a data-i18n="footer_about_us">关于我们</a><a data-i18n="footer_terms">用户协议</a><a data-i18n="footer_privacy">隐私政策</a><a data-i18n="footer_contact">联系合作</a>
</div>
</div>
<div class="container foot-bottom">© 2026 乐流 Music · 渐变流媒体风格模板 · 仅供学习演示</div>
<div class="container foot-bottom">© 2026 乐流 Music · <span data-i18n="footer_subtitle">渐变流媒体风格模板 · 仅供学习演示</span></div>
</footer>
<!-- 详情弹窗 -->
@@ -612,7 +656,7 @@ footer{
<div class="mini-player" id="miniPlayer">
<div class="mp-cover" id="mpCover"></div>
<div class="mp-info">
<h4 id="mpTitle">未在播放</h4>
<h4 id="mpTitle" data-i18n="player_idle_title">未在播放</h4>
<p id="mpArtist">—</p>
</div>
<div class="mp-controls">
@@ -635,6 +679,147 @@ footer{
<div class="toast" id="toast"></div>
<script>
/* ============================================================
i18n 国际化引擎
============================================================ */
const I18N_LOCALES = {
'zh-CN': {
title: '乐流 Music — 音乐无界,流光溢彩',
nav_home: '首页', nav_explore: '探索', nav_hot: '热门', nav_new: '新歌',
nav_like_title: '我的喜欢',
search_placeholder: '搜索歌曲、歌手、专辑…',
hero_title: '音乐无界,流光溢彩',
hero_subtitle: '沉浸于色彩的律动,让每一个音符都化作光影。千万曲库,一键聆听,开启你的专属音乐宇宙。',
hero_listen: '立即聆听', hero_browse: '浏览曲库',
stat_songs: '在线曲库', stat_quality: '无损音质', stat_daily_val: '每日更新', stat_daily: '新歌首发',
section_genres: '音乐分类', section_hot: '热门榜单', section_new: '新歌首发', section_plaza: '音乐广场',
see_all: '查看全部 ›', more_new: '更多新歌 ›',
sort_default: '默认', sort_plays: '播放最多', sort_likes: '收藏最多', sort_newest: '最新发布',
plaza_search_placeholder: '搜索歌曲…',
plaza_count: '共 ',
player_idle_title: '未在播放',
cat_all: '全部', cat_pop: '流行', cat_rock: '摇滚', cat_folk: '民谣',
cat_electronic: '电子', cat_hiphop: '嘻哈', cat_rnb: 'R&B', cat_classical: '古典', cat_jazz: '爵士',
empty_categories: '暂无分类', empty_hot: '暂无热门歌曲', empty_new: '暂无新歌',
empty_plaza: '没有找到相关歌曲,换个关键词试试吧',
toast_now_playing: '正在播放:', toast_select_song: '请先选择一首歌曲',
toast_cancel_like: '已取消收藏', toast_add_like: '已加入收藏',
toast_no_liked: '还没有收藏的歌曲,去点亮一颗心吧', toast_liked_count: '已收藏 ',
toast_no_songs: '暂无可播放的歌曲',
tracks_unit: '首',
modal_plays: '播放量', modal_likes: '收藏量', modal_duration: '时长',
modal_album_prefix: '专辑:《', modal_album_suffix: '》',
modal_lyrics: '歌词', modal_no_lyrics: '暂无歌词,纯音乐同样动人',
modal_play: '播放', modal_like: '收藏', modal_liked: '已收藏',
footer_desc: '音乐无界,流光溢彩。用色彩诠释旋律,用科技传递感动。',
footer_product: '产品', footer_discover: '发现', footer_about: '关于',
footer_play: '在线播放', footer_recommend: '个性推荐', footer_playlist: '歌单广场', footer_lossless: '无损音质',
footer_artist: '歌手专区',
footer_about_us: '关于我们', footer_terms: '用户协议', footer_privacy: '隐私政策', footer_contact: '联系合作',
footer_subtitle: '渐变流媒体风格模板 · 仅供学习演示',
},
'zh-TW': {
title: '樂流 Music — 音樂無界,流光溢彩',
nav_home: '首頁', nav_explore: '探索', nav_hot: '熱門', nav_new: '新歌',
nav_like_title: '我的喜歡',
search_placeholder: '搜尋歌曲、歌手、專輯…',
hero_title: '音樂無界,流光溢彩',
hero_subtitle: '沉浸於色彩的律動,讓每一個音符都化作光影。千萬曲庫,一鍵聆聽,開啟你的專屬音樂宇宙。',
hero_listen: '立即聆聽', hero_browse: '瀏覽曲庫',
stat_songs: '線上曲庫', stat_quality: '無損音質', stat_daily_val: '每日更新', stat_daily: '新歌首發',
section_genres: '音樂分類', section_hot: '熱門榜單', section_new: '新歌首發', section_plaza: '音樂廣場',
see_all: '查看全部 ›', more_new: '更多新歌 ›',
sort_default: '預設', sort_plays: '播放最多', sort_likes: '收藏最多', sort_newest: '最新發布',
plaza_search_placeholder: '搜尋歌曲…',
plaza_count: '共 ',
player_idle_title: '未在播放',
cat_all: '全部', cat_pop: '流行', cat_rock: '搖滾', cat_folk: '民謠',
cat_electronic: '電子', cat_hiphop: '嘻哈', cat_rnb: 'R&B', cat_classical: '古典', cat_jazz: '爵士',
empty_categories: '暫無分類', empty_hot: '暫無熱門歌曲', empty_new: '暫無新歌',
empty_plaza: '沒有找到相關歌曲,換個關鍵詞試試吧',
toast_now_playing: '正在播放:', toast_select_song: '請先選擇一首歌曲',
toast_cancel_like: '已取消收藏', toast_add_like: '已加入收藏',
toast_no_liked: '還沒有收藏的歌曲,去點亮一顆心吧', toast_liked_count: '已收藏 ',
toast_no_songs: '暫無可播放的歌曲',
tracks_unit: '首',
modal_plays: '播放量', modal_likes: '收藏量', modal_duration: '時長',
modal_album_prefix: '專輯:《', modal_album_suffix: '》',
modal_lyrics: '歌詞', modal_no_lyrics: '暫無歌詞,純音樂同樣動人',
modal_play: '播放', modal_like: '收藏', modal_liked: '已收藏',
footer_desc: '音樂無界,流光溢彩。用色彩詮釋旋律,用科技傳遞感動。',
footer_product: '產品', footer_discover: '發現', footer_about: '關於',
footer_play: '線上播放', footer_recommend: '個性推薦', footer_playlist: '歌單廣場', footer_lossless: '無損音質',
footer_artist: '歌手專區',
footer_about_us: '關於我們', footer_terms: '用戶協議', footer_privacy: '隱私政策', footer_contact: '聯繫合作',
footer_subtitle: '漸變流媒體風格模板 · 僅供學習演示',
},
'en': {
title: 'LeMusic — Music Without Borders',
nav_home: 'Home', nav_explore: 'Explore', nav_hot: 'Hot', nav_new: 'New',
nav_like_title: 'My Likes',
search_placeholder: 'Search songs, artists, albums…',
hero_title: 'Music Without Borders, Streaming in Color',
hero_subtitle: 'Immerse in the rhythm of colors, let every note become light. Millions of songs, one click to play,开启 your personal music universe.',
hero_listen: 'Listen Now', hero_browse: 'Browse Library',
stat_songs: 'Online Library', stat_quality: 'Lossless Audio', stat_daily_val: 'Daily Updates', stat_daily: 'New Releases',
section_genres: 'Genres', section_hot: 'Hot Charts', section_new: 'New Releases', section_plaza: 'Music Plaza',
see_all: 'See All ›', more_new: 'More New ›',
sort_default: 'Default', sort_plays: 'Most Played', sort_likes: 'Most Liked', sort_newest: 'Newest',
plaza_search_placeholder: 'Search songs…',
plaza_count: '',
player_idle_title: 'Nothing Playing',
cat_all: 'All', cat_pop: 'Pop', cat_rock: 'Rock', cat_folk: 'Folk',
cat_electronic: 'Electronic', cat_hiphop: 'Hip-Hop', cat_rnb: 'R&B', cat_classical: 'Classical', cat_jazz: 'Jazz',
empty_categories: 'No categories', empty_hot: 'No hot songs yet', empty_new: 'No new releases yet',
empty_plaza: 'No songs found, try different keywords',
toast_now_playing: 'Now playing: ', toast_select_song: 'Select a song first',
toast_cancel_like: 'Removed from liked', toast_add_like: 'Added to liked',
toast_no_liked: 'No liked songs yet, go show some love', toast_liked_count: 'Liked ',
toast_no_songs: 'No songs available',
tracks_unit: '',
modal_plays: 'Plays', modal_likes: 'Likes', modal_duration: 'Duration',
modal_album_prefix: 'Album: ', modal_album_suffix: '',
modal_lyrics: 'Lyrics', modal_no_lyrics: 'No lyrics available, instrumental music speaks for itself',
modal_play: 'Play', modal_like: 'Like', modal_liked: 'Liked',
footer_desc: 'Music without borders, streaming in color. Interpreting melody with color, delivering emotion with technology.',
footer_product: 'Product', footer_discover: 'Discover', footer_about: 'About',
footer_play: 'Online Play', footer_recommend: 'Recommendations', footer_playlist: 'Playlists', footer_lossless: 'Lossless Audio',
footer_artist: 'Artists',
footer_about_us: 'About Us', footer_terms: 'Terms', footer_privacy: 'Privacy', footer_contact: 'Contact',
footer_subtitle: 'Gradient streaming style template · For demo purposes only',
}
};
const LANG_LABELS = {'zh-CN':'中文','en':'EN','zh-TW':'繁中'};
const CATEGORY_NAME_KEY = {流行:'cat_pop',摇滚:'cat_rock',民谣:'cat_folk',电子:'cat_electronic',嘻哈:'cat_hiphop','R&B':'cat_rnb',古典:'cat_classical',爵士:'cat_jazz'};
let currentLang = (function(){
try{ var u=new URLSearchParams(location.search).get('lang'); if(u&&I18N_LOCALES[u]) return u; }catch(e){}
try{ var s=localStorage.getItem('music3_lang'); if(s&&I18N_LOCALES[s]) return s; }catch(e){}
var b=navigator.language||navigator.userLanguage||'';
if(b.startsWith('zh-TW')||b.startsWith('zh-Hant')) return 'zh-TW';
if(b.startsWith('zh')) return 'zh-CN';
return 'en';
})();
function t(key){ return (I18N_LOCALES[currentLang]&&I18N_LOCALES[currentLang][key])||(I18N_LOCALES['zh-CN'][key])||key; }
function applyI18n(){
document.querySelectorAll('[data-i18n]').forEach(function(el){ el.textContent=t(el.getAttribute('data-i18n')); });
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){ el.placeholder=t(el.getAttribute('data-i18n-placeholder')); });
document.querySelectorAll('[data-i18n-title]').forEach(function(el){ el.title=t(el.getAttribute('data-i18n-title')); });
document.documentElement.lang=currentLang==='zh-TW'?'zh-TW':currentLang==='zh-CN'?'zh-CN':'en';
document.title=t('title');
var ll=document.getElementById('langLabel'); if(ll) ll.textContent=LANG_LABELS[currentLang]||currentLang;
document.querySelectorAll('.lang-option').forEach(function(opt){ opt.classList.toggle('active',opt.getAttribute('data-lang')===currentLang); });
}
function switchLang(lang){
if(!I18N_LOCALES[lang]) return;
currentLang=lang;
try{localStorage.setItem('music3_lang',lang);}catch(e){}
applyI18n();
if(typeof onLangChange==='function') onLangChange(lang);
document.getElementById('langDropdown').classList.remove('open');
}
function toggleLangDropdown(){ document.getElementById('langDropdown').classList.toggle('open'); }
document.addEventListener('click',function(e){ var sw=document.getElementById('langSwitcher'); if(sw&&!sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open'); });
/* ============================================================
乐流 Music — 渐变流媒体风格 单文件模板
全部 API 使用 fetch,失败时回退到内置 Mock 数据以保证模板可独立预览
@@ -815,12 +1000,14 @@ async function renderCategories(){
const bar=document.getElementById('catBar');
bar.innerHTML='';
const all=document.createElement('div');
all.className='cat-pill active';all.textContent='全部';all.dataset.id='';
all.className='cat-pill active';all.textContent=t('cat_all');all.dataset.id='';
bar.appendChild(all);
cats.sort((a,b)=>(a.sort||0)-(b.sort||0)).forEach(c=>{
const nameKey=CATEGORY_NAME_KEY[c.name];
const displayName=nameKey?t(nameKey):c.name;
const el=document.createElement('div');
el.className='cat-pill';el.dataset.id=c.id;
el.innerHTML=`<span style="margin-right:5px">${c.icon||'🎵'}</span>${escapeHtml(c.name)}`;
el.innerHTML=`<span style="margin-right:5px">${c.icon||'🎵'}</span>${escapeHtml(displayName)}`;
bar.appendChild(el);
});
bar.querySelectorAll('.cat-pill').forEach(p=>{
@@ -839,7 +1026,7 @@ async function renderHot(){
const home=await apiHome();
const hot=home.hot||[];
const grid=document.getElementById('hotGrid');
if(!hot.length){ grid.innerHTML='<div class="empty-state">暂无热门歌曲</div>'; return; }
if(!hot.length){ grid.innerHTML='<div class="empty-state">'+t('empty_hot')+'</div>'; return; }
const top=hot[0];
let side1='', side2='';
for(let i=1;i<hot.length && i<5;i++){
@@ -880,7 +1067,7 @@ async function renderNew(){
const home=await apiHome();
const news=home.new||[];
const sc=document.getElementById('newScroll');
if(!news.length){ sc.innerHTML='<div class="empty-state">暂无新歌</div>'; return; }
if(!news.length){ sc.innerHTML='<div class="empty-state">'+t('empty_new')+'</div>'; return; }
sc.innerHTML=news.map(s=>`
<div class="new-card" data-id="${s.id}">
<div class="nc-cover">
@@ -908,11 +1095,11 @@ async function renderPlaza(){
grid.innerHTML=sk;
const data=await apiSongs({category_id:currentCat,keyword:plazaState.keyword,sort:plazaState.sort,page:plazaState.page,page_size:plazaState.page_size});
plazaState.total=data.total||0;
document.getElementById('plazaCount').textContent=`共 ${data.total} 首`;
document.getElementById('plazaCount').textContent=t('plaza_count')+data.total+' '+t('tracks_unit');
if(!data.list.length){
grid.innerHTML=`<div class="empty-state">
<svg viewBox="0 0 24 24" fill="rgba(255,255,255,0.4)"><path d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z"/></svg>
<div>没有找到相关歌曲,换个关键词试试吧</div></div>`;
<div>${t('empty_plaza')}</div></div>`;
}else{
grid.innerHTML=data.list.map(s=>songCardHTML(s)).join('');
bindSongClicks(grid);
@@ -997,20 +1184,20 @@ async function openModal(id){
<span class="m-cat">${escapeHtml(s.category_name||'音乐')}</span>
<h2>${escapeHtml(s.title)}</h2>
<div class="m-artist">${escapeHtml(s.artist)}</div>
<div class="m-album">专辑:《${escapeHtml(s.album||'未知')}》· ${escapeHtml(s.language||'')} · ${escapeHtml(s.release_date||'')}</div>
<div class="m-album">${t('modal_album_prefix')}${escapeHtml(s.album||'未知')}${t('modal_album_suffix')}· ${escapeHtml(s.language||'')} · ${escapeHtml(s.release_date||'')}</div>
<div class="modal-tags">
${tags.map(t=>`<span class="tg">#${escapeHtml(t)}</span>`).join('')||'<span class="tg">#音乐</span>'}
</div>
<div class="modal-stats">
<div class="ms"><b class="grad-text">${formatNumber(s.play_count)}</b><span>播放量</span></div>
<div class="ms"><b class="grad-text">${formatNumber(s.like_count)}</b><span>收藏量</span></div>
<div class="ms"><b class="grad-text">${formatDuration(s.duration)}</b><span>时长</span></div>
<div class="ms"><b class="grad-text">${formatNumber(s.play_count)}</b><span>${t('modal_plays')}</span></div>
<div class="ms"><b class="grad-text">${formatNumber(s.like_count)}</b><span>${t('modal_likes')}</span></div>
<div class="ms"><b class="grad-text">${formatDuration(s.duration)}</b><span>${t('modal_duration')}</span></div>
</div>
<div class="modal-actions">
<button class="btn-play-glow" id="modalPlayBtn">
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>播放
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>${t('modal_play')}
</button>
<button class="btn-like ${liked?'liked':''}" id="modalLikeBtn" title="收藏">
<button class="btn-like ${liked?'liked':''}" id="modalLikeBtn" title="${t('modal_like')}">
<svg viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
</button>
</div>
@@ -1018,8 +1205,8 @@ async function openModal(id){
</div>
<div class="modal-body">
<div class="lyrics-block">
<h5><span class="bar"></span>歌词</h5>
${lyrics.length?lyrics.map(l=>`<div class="lyrics-line">${escapeHtml(l)}</div>`).join(''):'<div class="lyrics-empty">暂无歌词,纯音乐同样动人</div>'}
<h5><span class="bar"></span>${t('modal_lyrics')}</h5>
${lyrics.length?lyrics.map(l=>`<div class="lyrics-line">${escapeHtml(l)}</div>`).join(''):`<div class="lyrics-empty">${t('modal_no_lyrics')}</div>`}
</div>
</div>`;
document.getElementById('modalClose').addEventListener('click',closeModal);
@@ -1030,8 +1217,8 @@ async function openModal(id){
});
document.getElementById('modalLikeBtn').addEventListener('click',function(){
const sid=parseInt(s.id,10);
if(likedSet.has(sid)){ likedSet.delete(sid); this.classList.remove('liked'); toast('已取消收藏'); }
else{ likedSet.add(sid); this.classList.add('liked'); toast('已加入收藏'); updateMiniLike(); }
if(likedSet.has(sid)){ likedSet.delete(sid); this.classList.remove('liked'); toast(t('toast_cancel_like')); }
else{ likedSet.add(sid); this.classList.add('liked'); toast(t('toast_add_like')); updateMiniLike(); }
});
}
function closeModal(){
@@ -1053,7 +1240,7 @@ function playSong(song){
updateMiniUI();
startProgress();
document.getElementById('miniPlayer').classList.add('show');
toast('正在播放:'+song.title);
toast(t('toast_now_playing')+song.title);
}
let lastPlazaList=[];
function updateMiniUI(){
@@ -1094,7 +1281,7 @@ function startProgress(){
},1000);
}
function togglePlay(){
if(!player.song){ toast('请先选择一首歌曲'); return; }
if(!player.song){ toast(t('toast_select_song')); return; }
player.playing=!player.playing;
setPlayIcon(player.playing);
if(player.playing) startProgress(); else clearInterval(player.timer);
@@ -1115,10 +1302,10 @@ document.getElementById('mpPlay').addEventListener('click',togglePlay);
document.getElementById('mpNext').addEventListener('click',nextSong);
document.getElementById('mpPrev').addEventListener('click',prevSong);
document.getElementById('mpLike').addEventListener('click',function(){
if(!player.song){ toast('请先选择一首歌曲'); return; }
if(!player.song){ toast(t('toast_select_song')); return; }
const sid=parseInt(player.song.id,10);
if(likedSet.has(sid)){ likedSet.delete(sid); this.classList.remove('liked'); toast('已取消收藏'); }
else{ likedSet.add(sid); this.classList.add('liked'); toast('已加入收藏'); }
if(likedSet.has(sid)){ likedSet.delete(sid); this.classList.remove('liked'); toast(t('toast_cancel_like')); }
else{ likedSet.add(sid); this.classList.add('liked'); toast(t('toast_add_like')); }
});
document.getElementById('mpBar').addEventListener('click',function(e){
if(!player.song) return;
@@ -1170,7 +1357,7 @@ document.querySelectorAll('.nav-links a').forEach(a=>{
document.getElementById('heroListenBtn').addEventListener('click',async()=>{
const home=await apiHome();
if(home.hot&&home.hot[0]) playSong(home.hot[0]);
else toast('暂无可播放的歌曲');
else toast(t('toast_no_songs'));
});
document.getElementById('heroExploreBtn').addEventListener('click',()=>{
document.getElementById('plaza').scrollIntoView({behavior:'smooth'});
@@ -1179,8 +1366,8 @@ document.querySelectorAll('[data-scroll]').forEach(el=>{
el.addEventListener('click',()=>{ document.getElementById(el.dataset.scroll).scrollIntoView({behavior:'smooth'}); });
});
document.getElementById('navLikeBtn').addEventListener('click',()=>{
if(!likedSet.size){ toast('还没有收藏的歌曲,去点亮一颗心吧'); return; }
toast('已收藏 '+likedSet.size+' 首歌曲');
if(!likedSet.size){ toast(t('toast_no_liked')); return; }
toast(t('toast_liked_count')+likedSet.size+' '+t('tracks_unit'));
});
/* ---------- 滚动动画 ---------- */
@@ -1204,8 +1391,18 @@ document.querySelectorAll('.reveal').forEach(el=>io.observe(el));
}
})();
/* ---------- Language Change Callback ---------- */
function onLangChange(lang){
applyI18n();
renderCategories();
renderHot();
renderNew();
renderPlaza();
}
/* ---------- 初始化 ---------- */
(async function init(){
applyI18n();
renderCategories();
renderHot();
renderNew();