Browse Source

feat: added german translation and in the process a new translation system, close #1221, credit @Totto16

DIYgod 2 years ago
parent
commit
ef273ec42a
8 changed files with 285 additions and 160 deletions
  1. 2 1
      demo/demo.js
  2. 1 1
      src/js/comment.js
  3. 2 2
      src/js/controller.js
  4. 2 2
      src/js/danmaku.js
  5. 244 120
      src/js/i18n.js
  6. 2 2
      src/js/options.js
  7. 7 7
      src/js/player.js
  8. 25 25
      src/template/player.art

+ 2 - 1
demo/demo.js

@@ -88,6 +88,7 @@ function initPlayers() {
         logo: 'https://i.loli.net/2019/06/06/5cf8c5d94521136430.png',
         volume: 0.2,
         mutex: true,
+        lang: 'zh-cn',
         video: {
             url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',
             pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',
@@ -140,7 +141,7 @@ function initPlayers() {
     const eventsEle = document.getElementById('events');
     for (let i = 0; i < events.length; i++) {
         dp2.on(events[i], (info) => {
-            eventsEle.innerHTML += '<p>Event: ' + events[i] + '</p>';
+            eventsEle.innerHTML += `<p>Event: ${events[i]} ${info?`Data: <span>${JSON.stringify(info)}</span>`:''}</p>`;
             eventsEle.scrollTop = eventsEle.scrollHeight;
         });
     }

+ 1 - 1
src/js/comment.js

@@ -76,7 +76,7 @@ class Comment {
 
         // text can't be empty
         if (!this.player.template.commentInput.value.replace(/^\s+|\s+$/g, '')) {
-            this.player.notice(this.player.tran('Please input danmaku content!'));
+            this.player.notice(this.player.tran('please-input-danmaku'));
             return;
         }
 

+ 2 - 2
src/js/controller.js

@@ -366,12 +366,12 @@ class Controller {
     initSubtitleButton() {
         if (this.player.options.subtitle) {
             this.player.events.on('subtitle_show', () => {
-                this.player.template.subtitleButton.dataset.balloon = this.player.tran('Hide subtitle');
+                this.player.template.subtitleButton.dataset.balloon = this.player.tran('hide-subs');
                 this.player.template.subtitleButtonInner.style.opacity = '';
                 this.player.user.set('subtitle', 1);
             });
             this.player.events.on('subtitle_hide', () => {
-                this.player.template.subtitleButton.dataset.balloon = this.player.tran('Show subtitle');
+                this.player.template.subtitleButton.dataset.balloon = this.player.tran('show-subs');
                 this.player.template.subtitleButtonInner.style.opacity = '0.4';
                 this.player.user.set('subtitle', 0);
             });

+ 2 - 2
src/js/danmaku.js

@@ -70,7 +70,7 @@ class Danmaku {
                     }
                 },
                 error: (msg) => {
-                    this.options.error(msg || this.options.tran('Danmaku load failed'));
+                    this.options.error(msg || this.options.tran('danmaku-failed'));
                     results[i] = [];
 
                     ++readCount;
@@ -97,7 +97,7 @@ class Danmaku {
             data: danmakuData,
             success: callback,
             error: (msg) => {
-                this.options.error(msg || this.options.tran('Danmaku send failed'));
+                this.options.error(msg || this.options.tran('danmaku-failed'));
             },
         });
 

+ 244 - 120
src/js/i18n.js

@@ -9,139 +9,263 @@ Use this as shown below..... */
 
 function i18n(lang) {
     this.lang = lang;
-    this.tran = (text) => {
-        if (tranTxt[this.lang] && tranTxt[this.lang][text]) {
-            return tranTxt[this.lang][text];
+    // in case someone says en-us, and en is present!
+    this.fallbackLang = this.lang.includes('-') ? this.lang.split('-')[0] : this.lang;
+    this.tran = (key) => {
+        key = key.toLowerCase();
+        if (tranTxt[this.lang] && tranTxt[this.lang][key]) {
+            return tranTxt[this.lang][key];
+        } else if (tranTxt[this.fallbackLang] && tranTxt[this.fallbackLang][key]) {
+            return tranTxt[this.fallbackLang][key];
         } else {
-            return text;
+            return standard[key];
         }
     };
 }
 
+// abstract model for recognizing if valid translations are present
+// const model = {
+//     'danmaku-loading': [],
+//     top: [],
+//     bottom: [],
+//     rolling: [],
+//     'input-danmaku-enter': [],
+//     'about-author': [],
+//     'dplayer-feedback': [],
+//     'about-dplayer': [],
+//     loop: [],
+//     speed: [],
+//     'opacity-danmaku': [],
+//     normal: [],
+//     'please-input-danmaku': [],
+//     'set-danmaku-color': [],
+//     'set-danmaku-type': [],
+//     'show-danmaku': [],
+//     'video-failed': [],
+//     'danmaku-failed': [],
+//     'danmaku-send-failed': [],
+//     'switching-quality': [{ symbol: '%q', name: 'Quality', example: '720p' }],
+//     'switched-quality': [{ symbol: '%q', name: 'Quality', example: '720p' }],
+//     ff: [{ symbol: '%s', name: 'Seconds', example: '5' }],
+//     rew: [{ symbol: '%s', name: 'Seconds', example: '5' }],
+//     'unlimited-danmaku': [],
+//     'send-danmaku': [],
+//     setting: [],
+//     fullscreen: [],
+//     'web-fullscreen': [],
+//     send: [],
+//     screenshot: [],
+//     airplay: [],
+//     chromecast: [],
+//     'show-subs': [],
+//     'hide-subs': [],
+//     volume: [],
+//     live: [],
+//     'video-info': [],
+// };
+
+// Standard english translations
+const standard = {
+    'danmaku-loading': 'Danmaku is loading',
+    top: 'Top',
+    bottom: 'Bottom',
+    rolling: 'Rolling',
+    'input-danmaku-enter': 'Input danmaku, hit Enter',
+    'about-author': 'About author',
+    'dplayer-feedback': 'DPlayer feedback',
+    'about-dplayer': 'About DPlayer',
+    loop: 'Loop',
+    speed: 'Speed',
+    'opacity-danmaku': 'Opacity for danmaku',
+    normal: 'Normal',
+    'please-input-danmaku': 'Please input danmaku content!',
+    'set-danmaku-color': 'Set danmaku color',
+    'set-danmaku-type': 'Set danmaku type',
+    'show-danmaku': 'Show danmaku',
+    'video-failed': 'Video load failed',
+    'danmaku-failed': 'Danmaku load failed',
+    'danmaku-send-failed': 'Danmaku send failed',
+    'switching-quality': 'Switching to %q quality',
+    'switched-quality': 'Switched to %q quality',
+    ff: 'FF %s s',
+    rew: 'REW %s s',
+    'unlimited-danmaku': 'Unlimited danmaku',
+    'send-danmaku': 'Send danmaku',
+    setting: 'Setting',
+    fullscreen: 'Full screen',
+    'web-fullscreen': 'Web full screen',
+    send: 'Send',
+    screenshot: 'Screenshot',
+    airplay: 'AirPlay',
+    chromecast: 'ChromeCast',
+    'show-subs': 'Show subtitle',
+    'hide-subs': 'Hide subtitle',
+    volume: 'Volume',
+    live: 'Live',
+    'video-info': 'Video info',
+};
+
 // add translation text here
 const tranTxt = {
+    en: standard,
     'zh-cn': {
-        'Danmaku is loading': '弹幕加载中',
-        Top: '顶部',
-        Bottom: '底部',
-        Rolling: '滚动',
-        'Input danmaku, hit Enter': '输入弹幕,回车发送',
-        'About author': '关于作者',
-        'DPlayer feedback': '播放器意见反馈',
-        'About DPlayer': '关于 DPlayer 播放器',
-        Loop: '洗脑循环',
-        Speed: '速度',
-        'Opacity for danmaku': '弹幕透明度',
-        Normal: '正常',
-        'Please input danmaku content!': '要输入弹幕内容啊喂!',
-        'Set danmaku color': '设置弹幕颜色',
-        'Set danmaku type': '设置弹幕类型',
-        'Show danmaku': '显示弹幕',
-        'Video load failed': '视频加载失败',
-        'Danmaku load failed': '弹幕加载失败',
-        'Danmaku send failed': '弹幕发送失败',
-        'Switching to': '正在切换至',
-        'Switched to': '已经切换至',
-        quality: '画质',
-        FF: '快进',
-        REW: '快退',
-        'Unlimited danmaku': '海量弹幕',
-        'Send danmaku': '发送弹幕',
-        Setting: '设置',
-        'Full screen': '全屏',
-        'Web full screen': '页面全屏',
-        Send: '发送',
-        Screenshot: '截图',
-        AirPlay: '无线投屏',
-        ChromeCast: 'ChromeCast',
-        s: '秒',
-        'Show subtitle': '显示字幕',
-        'Hide subtitle': '隐藏字幕',
-        Volume: '音量',
-        Live: '直播',
-        'Video info': '视频统计信息',
+        'danmaku-loading': '弹幕加载中',
+        top: '顶部',
+        bottom: '底部',
+        rolling: '滚动',
+        'input-danmaku-enter': '输入弹幕,回车发送',
+        'about-author': '关于作者',
+        'dplayer-feedback': '播放器意见反馈',
+        'about-dplayer': '关于 DPlayer 播放器',
+        loop: '洗脑循环',
+        speed: '速度',
+        'opacity-danmaku': '弹幕透明度',
+        normal: '正常',
+        'please-input-danmaku': '要输入弹幕内容啊喂!',
+        'set-danmaku-color': '设置弹幕颜色',
+        'set-danmaku-type': '设置弹幕类型',
+        'show-danmaku': '显示弹幕',
+        'video-failed': '视频加载失败',
+        'danmaku-failed': '弹幕加载失败',
+        'danmaku-send-failed': '弹幕发送失败',
+        'switching-quality': '正在切换至 %q 画质',
+        'switched-quality': '已经切换至 %q 画质',
+        ff: '快进 %s 秒',
+        rew: '快退 %s 秒',
+        'unlimited-danmaku': '海量弹幕',
+        'send-danmaku': '发送弹幕',
+        setting: '设置',
+        fullscreen: '全屏',
+        'web-fullscreen': '页面全屏',
+        send: '发送',
+        screenshot: '截图',
+        airplay: '无线投屏',
+        chromecast: 'ChromeCast',
+        'show-subs': '显示字幕',
+        'hide-subs': '隐藏字幕',
+        volume: '音量',
+        live: '直播',
+        'video-info': '视频统计信息',
     },
     'zh-tw': {
-        'Danmaku is loading': '彈幕載入中',
-        Top: '頂部',
-        Bottom: '底部',
-        Rolling: '滾動',
-        'Input danmaku, hit Enter': '輸入彈幕,Enter 發送',
-        'About author': '關於作者',
-        'DPlayer feedback': '播放器意見回饋',
-        'About DPlayer': '關於 DPlayer 播放器',
-        Loop: '循環播放',
-        Speed: '速度',
-        'Opacity for danmaku': '彈幕透明度',
-        Normal: '正常',
-        'Please input danmaku content!': '請輸入彈幕內容啊!',
-        'Set danmaku color': '設定彈幕顏色',
-        'Set danmaku type': '設定彈幕類型',
-        'Show danmaku': '顯示彈幕',
-        'Video load failed': '影片載入失敗',
-        'Danmaku load failed': '彈幕載入失敗',
-        'Danmaku send failed': '彈幕發送失敗',
-        'Switching to': '正在切換至',
-        'Switched to': '已經切換至',
-        quality: '畫質',
-        FF: '快進',
-        REW: '快退',
-        'Unlimited danmaku': '巨量彈幕',
-        'Send danmaku': '發送彈幕',
-        Setting: '設定',
-        'Full screen': '全螢幕',
-        'Web full screen': '頁面全螢幕',
-        Send: '發送',
-        Screenshot: '截圖',
-        AirPlay: '無線投屏',
-        ChromeCast: 'ChromeCast',
-        s: '秒',
-        'Show subtitle': '顯示字幕',
-        'Hide subtitle': '隱藏字幕',
-        Volume: '音量',
-        Live: '直播',
-        'Video info': '影片統計訊息',
+        'danmaku-loading': '彈幕載入中',
+        top: '頂部',
+        bottom: '底部',
+        rolling: '滾動',
+        'input-danmaku-enter': '輸入彈幕,Enter 發送',
+        'about-author': '關於作者',
+        'dplayer-feedback': '播放器意見回饋',
+        'about-dplayer': '關於 DPlayer 播放器',
+        loop: '循環播放',
+        speed: '速度',
+        'opacity-danmaku': '彈幕透明度',
+        normal: '正常',
+        'please-input-danmaku': '請輸入彈幕內容啊!',
+        'set-danmaku-color': '設定彈幕顏色',
+        'set-danmaku-type': '設定彈幕類型',
+        'show-danmaku': '顯示彈幕',
+        'video-failed': '影片載入失敗',
+        'danmaku-failed': '彈幕載入失敗',
+        'danmaku-send-failed': '彈幕發送失敗',
+        'switching-quality': '正在切換至 %q 畫質',
+        'switched-quality': '已經切換至 %q 畫質',
+        ff: '快進 %s 秒',
+        rew: '快退 %s 秒',
+        'unlimited-danmaku': '巨量彈幕',
+        'send-danmaku': '發送彈幕',
+        setting: '設定',
+        fullscreen: '全螢幕',
+        'web-fullscreen': '頁面全螢幕',
+        send: '發送',
+        screenshot: '截圖',
+        airplay: '無線投屏',
+        chromecast: 'ChromeCast',
+        'show-subs': '顯示字幕',
+        'hide-subs': '隱藏字幕',
+        volume: '音量',
+        live: '直播',
+        'video-info': '影片統計訊息',
     },
     'ko-kr': {
-        'Danmaku is loading': 'Danmaku를 불러오는 중입니다.',
-        Top: 'Top',
-        Bottom: 'Bottom',
-        Rolling: 'Rolling',
-        'Input danmaku, hit Enter': 'Danmaku를 입력하고 Enter를 누르세요.',
-        'About author': '만든이',
-        'DPlayer feedback': '피드백 보내기',
-        'About DPlayer': 'DPlayer 정보',
-        Loop: '반복',
-        Speed: '배속',
-        'Opacity for danmaku': 'Danmaku 불투명도',
-        Normal: '표준',
-        'Please input danmaku content!': 'Danmaku를 입력하세요!',
-        'Set danmaku color': 'Danmaku 색상',
-        'Set danmaku type': 'Danmaku 설정',
-        'Show danmaku': 'Danmaku 표시',
-        'Video load failed': '비디오를 불러오지 못했습니다.',
-        'Danmaku load failed': 'Danmaku를 불러오지 못했습니다.',
-        'Danmaku send failed': 'Danmaku 전송에 실패했습니다.',
-        'Switching to': '전환',
-        'Switched to': '전환 됨',
-        quality: '화질',
-        FF: '앞으로',
-        REW: '뒤로',
-        'Unlimited danmaku': '끝없는 Danmaku',
-        'Send danmaku': 'Danmaku 보내기',
-        Setting: '설정',
-        'Full screen': '전체 화면',
-        'Web full screen': '웹 내 전체화면',
-        Send: '보내기',
-        Screenshot: '화면 캡쳐',
-        AirPlay: '에어플레이',
-        s: '초',
-        'Show subtitle': '자막 보이기',
-        'Hide subtitle': '자막 숨기기',
+        'danmaku-loading': 'Danmaku를 불러오는 중입니다.',
+        top: 'Top',
+        bottom: 'Bottom',
+        rolling: 'Rolling',
+        'input-danmaku-enter': 'Danmaku를 입력하고 Enter를 누르세요.',
+        'about-author': '만든이',
+        'dplayer-feedback': '피드백 보내기',
+        'about-dplayer': 'DPlayer 정보',
+        loop: '반복',
+        speed: '배속',
+        'opacity-danmaku': 'Danmaku 불투명도',
+        normal: '표준',
+        'please-input-danmaku': 'Danmaku를 입력하세요!',
+        'set-danmaku-color': 'Danmaku 색상',
+        'set-danmaku-type': 'Danmaku 설정',
+        'show-danmaku': 'Danmaku 표시',
+        'video-failed': '비디오를 불러오지 못했습니다.',
+        'danmaku-failed': 'Danmaku를 불러오지 못했습니다.',
+        'danmaku-send-failed': 'Danmaku 전송에 실패했습니다.',
+        'Switching to': '',
+        'Switched to': '',
+        'switching-quality': '전환 %q 화질',
+        'switched-quality': '전환 됨 %q 화질',
+        ff: '앞으로 %s 초',
+        rew: '뒤로 %s 초',
+        'unlimited-danmaku': '끝없는 Danmaku',
+        'send-danmaku': 'Danmaku 보내기',
+        setting: '설정',
+        fullscreen: '전체 화면',
+        'web-fullscreen': '웹 내 전체화면',
+        send: '보내기',
+        screenshot: '화면 캡쳐',
+        airplay: '에어플레이',
+        chromecast: 'ChromeCast',
+        'show-subs': '자막 보이기',
+        'hide-subs': '자막 숨기기',
         Volume: '볼륨',
-        Live: '생방송',
-        'Video info': '비디오 정보',
+        live: '생방송',
+        'video-info': '비디오 정보',
+    },
+    de: {
+        'danmaku-loading': 'Danmaku lädt...',
+        top: 'Oben',
+        bottom: 'Unten',
+        rolling: 'Rollend',
+        'input-danmaku-enter': 'Drücke Enter nach dem Einfügen vom Danmaku',
+        'about-author': 'Über den Autor',
+        'dplayer-feedback': 'DPlayer Feedback',
+        'about-dplayer': 'Über DPlayer',
+        loop: 'Wiederholen',
+        speed: 'Geschwindigkeit',
+        'opacity-danmaku': 'Transparenz für Danmaku',
+        normal: 'Normal',
+        'please-input-danmaku': 'Bitte Danmaku Inhalt eingeben!',
+        'set-danmaku-color': 'Danmaku Farbe festlegen',
+        'set-danmaku-type': 'Danmaku Typ festlegen',
+        'show-danmaku': 'Zeige Danmaku',
+        'video-failed': 'Das Video konnte nicht geladen werden',
+        'danmaku-failed': 'Danmaku konnte nicht geladen werden',
+        'danmaku-send-failed': 'Das senden von Danmaku ist fehgeschlagen',
+        'switching-quality': 'Wechsle zur %q Qualität',
+        'switched-quality': 'Zur %q Qualität gewechselt',
+        ff: '%s s Vorwärts',
+        rew: '%s s Zurück',
+        'unlimited-danmaku': 'Unlimitiertes Danmaku',
+        'send-danmaku': 'Sende Danmaku',
+        setting: 'Einstellungen',
+        fullscreen: 'Vollbild',
+        'web-fullscreen': 'Browser Vollbild',
+        send: 'Senden',
+        screenshot: 'Screenshot',
+        airplay: 'AirPlay',
+        'show-subs': 'Zeige Untertitel',
+        chromecast: 'ChromeCast',
+        'hide-subs': 'Verstecke Untertitel',
+        volume: 'Lautstärke',
+        live: 'Live',
+        'video-info': 'Video Info',
     },
 };
 
-export default i18n;
+export { i18n };

+ 2 - 2
src/js/options.js

@@ -52,13 +52,13 @@ export default (options) => {
 
     options.contextmenu = options.contextmenu.concat([
         {
-            text: 'Video info',
+            key: 'video-info',
             click: (player) => {
                 player.infoPanel.triggle();
             },
         },
         {
-            text: 'About author',
+            key: 'about-author',
             link: 'https://diygod.me',
         },
         {

+ 7 - 7
src/js/player.js

@@ -2,7 +2,7 @@ import Promise from 'promise-polyfill';
 
 import utils from './utils';
 import handleOption from './options';
-import i18n from './i18n';
+import { i18n } from './i18n';
 import Template from './template';
 import Icons from './icons';
 import Danmaku from './danmaku';
@@ -156,9 +156,9 @@ class DPlayer {
             time = Math.min(time, this.video.duration);
         }
         if (this.video.currentTime < time) {
-            this.notice(`${this.tran('FF')} ${(time - this.video.currentTime).toFixed(0)} ${this.tran('s')}`);
+            this.notice(`${this.tran('ff').replace('%s', (time - this.video.currentTime).toFixed(0))}`);
         } else if (this.video.currentTime > time) {
-            this.notice(`${this.tran('REW')} ${(this.video.currentTime - time).toFixed(0)} ${this.tran('s')}`);
+            this.notice(`${this.tran('rew').replace('%s', (this.video.currentTime - time).toFixed(0))}`);
         }
 
         this.video.currentTime = time;
@@ -255,7 +255,7 @@ class DPlayer {
                 this.user.set('volume', percentage);
             }
             if (!nonotice) {
-                this.notice(`${this.tran('Volume')} ${(percentage * 100).toFixed(0)}%`);
+                this.notice(`${this.tran('volume')} ${(percentage * 100).toFixed(0)}%`);
             }
 
             this.video.volume = percentage;
@@ -468,7 +468,7 @@ class DPlayer {
                 // Not a video load error, may be poster load failed, see #307
                 return;
             }
-            this.tran && this.notice && this.type !== 'webtorrent' && this.notice(this.tran('Video load failed'), -1);
+            this.tran && this.notice && this.type !== 'webtorrent' && this.notice(this.tran('video-failed'), -1);
         });
 
         // video end
@@ -549,7 +549,7 @@ class DPlayer {
         this.video = videoEle;
         this.initVideo(this.video, this.quality.type || this.options.video.type);
         this.seek(this.prevVideo.currentTime);
-        this.notice(`${this.tran('Switching to')} ${this.quality.name} ${this.tran('quality')}`, -1);
+        this.notice(`${this.tran('switching-quality').replace('%q', this.quality.name)}`, -1);
         this.events.trigger('quality_start', this.quality);
 
         this.on('canplay', () => {
@@ -564,7 +564,7 @@ class DPlayer {
                     this.video.play();
                 }
                 this.prevVideo = null;
-                this.notice(`${this.tran('Switched to')} ${this.quality.name} ${this.tran('quality')}`);
+                this.notice(`${this.tran('switched-quality').replace('%q', this.quality.name)}`);
                 this.switchingQuality = false;
 
                 this.events.trigger('quality_end');

+ 25 - 25
src/template/player.art

@@ -13,7 +13,7 @@
     <div class="dplayer-bezel">
         <span class="dplayer-bezel-icon"></span>
         {{ if options.danmaku }}
-        <span class="dplayer-danloading">{{ tran('Danmaku is loading') }}</span>
+        <span class="dplayer-danloading">{{ tran('danmaku-loading') }}</span>
         {{ /if }}
         <span class="diplayer-loading-icon">{{@ icons.loading }}</span>
     </div>
@@ -21,12 +21,12 @@
 <div class="dplayer-controller-mask"></div>
 <div class="dplayer-controller">
     <div class="dplayer-icons dplayer-comment-box">
-        <button class="dplayer-icon dplayer-comment-setting-icon" data-balloon="{{ tran('Setting') }}" data-balloon-pos="up">
+        <button class="dplayer-icon dplayer-comment-setting-icon" data-balloon="{{ tran('setting') }}" data-balloon-pos="up">
             <span class="dplayer-icon-content">{{@ icons.pallette }}</span>
         </button>
         <div class="dplayer-comment-setting-box">
             <div class="dplayer-comment-setting-color">
-                <div class="dplayer-comment-setting-title">{{ tran('Set danmaku color') }}</div>
+                <div class="dplayer-comment-setting-title">{{ tran('set-danmaku-color') }}</div>
                 <label>
                     <input type="radio" name="dplayer-danmaku-color-{{ index }}" value="#fff" checked>
                     <span style="background: #fff;"></span>
@@ -53,23 +53,23 @@
                 </label>
             </div>
             <div class="dplayer-comment-setting-type">
-                <div class="dplayer-comment-setting-title">{{ tran('Set danmaku type') }}</div>
+                <div class="dplayer-comment-setting-title">{{ tran('set-danmaku-type') }}</div>
                 <label>
                     <input type="radio" name="dplayer-danmaku-type-{{ index }}" value="1">
-                    <span>{{ tran('Top') }}</span>
+                    <span>{{ tran('top') }}</span>
                 </label>
                 <label>
                     <input type="radio" name="dplayer-danmaku-type-{{ index }}" value="0" checked>
-                    <span>{{ tran('Rolling') }}</span>
+                    <span>{{ tran('rolling') }}</span>
                 </label>
                 <label>
                     <input type="radio" name="dplayer-danmaku-type-{{ index }}" value="2">
-                    <span>{{ tran('Bottom') }}</span>
+                    <span>{{ tran('bottom') }}</span>
                 </label>
             </div>
         </div>
-        <input class="dplayer-comment-input" type="text" placeholder="{{ tran('Input danmaku, hit Enter') }}" maxlength="30">
-        <button class="dplayer-icon dplayer-send-icon" data-balloon="{{ tran('Send') }}" data-balloon-pos="up">
+        <input class="dplayer-comment-input" type="text" placeholder="{{ tran('input-danmaku-enter') }}" maxlength="30">
+        <button class="dplayer-icon dplayer-send-icon" data-balloon="{{ tran('send') }}" data-balloon-pos="up">
             <span class="dplayer-icon-content">{{@ icons.send }}</span>
         </button>
     </div>
@@ -94,7 +94,7 @@
             <span class="dplayer-dtime">0:00</span>
         </span>
         {{ if options.live }}
-        <span class="dplayer-live-badge"><span class="dplayer-live-dot" style="background: {{ options.theme }};"></span>{{ tran('Live') }}</span>
+        <span class="dplayer-live-badge"><span class="dplayer-live-dot" style="background: {{ options.theme }};"></span>{{ tran('live') }}</span>
         {{ /if }}
     </div>
     <div class="dplayer-icons dplayer-icons-right">
@@ -111,65 +111,65 @@
         </div>
         {{ /if }}
         {{ if options.screenshot }}
-        <div class="dplayer-icon dplayer-camera-icon" data-balloon="{{ tran('Screenshot') }}" data-balloon-pos="up">
+        <div class="dplayer-icon dplayer-camera-icon" data-balloon="{{ tran('screenshot') }}" data-balloon-pos="up">
             <span class="dplayer-icon-content">{{@ icons.camera }}</span>
         </div>
         {{ /if }}
         {{ if options.airplay }}
-        <div class="dplayer-icon dplayer-airplay-icon" data-balloon="{{ tran('AirPlay') }}" data-balloon-pos="up">
+        <div class="dplayer-icon dplayer-airplay-icon" data-balloon="{{ tran('airplay') }}" data-balloon-pos="up">
             <span class="dplayer-icon-content">{{@ icons.airplay }}</span>
         </div>
         {{ /if }}
         {{ if options.chromecast }}
-        <div class="dplayer-icon dplayer-chromecast-icon" data-balloon="{{ tran('ChromeCast') }}" data-balloon-pos="up">
+        <div class="dplayer-icon dplayer-chromecast-icon" data-balloon="{{ tran('chromecast') }}" data-balloon-pos="up">
             <span class="dplayer-icon-content">{{@ icons.chromecast }}</span>
         </div>
         {{ /if }}
         <div class="dplayer-comment">
-            <button class="dplayer-icon dplayer-comment-icon" data-balloon="{{ tran('Send danmaku') }}" data-balloon-pos="up">
+            <button class="dplayer-icon dplayer-comment-icon" data-balloon="{{ tran('send-danmaku') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.comment }}</span>
             </button>
         </div>
         {{ if options.subtitle }}
         <div class="dplayer-subtitle-btn">
-            <button class="dplayer-icon dplayer-subtitle-icon" data-balloon="{{ tran('Hide subtitle') }}" data-balloon-pos="up">
+            <button class="dplayer-icon dplayer-subtitle-icon" data-balloon="{{ tran('hide-subs') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.subtitle }}</span>
             </button>
         </div>
         {{ /if }}
         <div class="dplayer-setting">
-            <button class="dplayer-icon dplayer-setting-icon" data-balloon="{{ tran('Setting') }}" data-balloon-pos="up">
+            <button class="dplayer-icon dplayer-setting-icon" data-balloon="{{ tran('setting') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.setting }}</span>
             </button>
             <div class="dplayer-setting-box">
                 <div class="dplayer-setting-origin-panel">
                     <div class="dplayer-setting-item dplayer-setting-speed">
-                        <span class="dplayer-label">{{ tran('Speed') }}</span>
+                        <span class="dplayer-label">{{ tran('speed') }}</span>
                         <div class="dplayer-toggle">{{@ icons.right }}</div>
                     </div>
                     <div class="dplayer-setting-item dplayer-setting-loop">
-                        <span class="dplayer-label">{{ tran('Loop') }}</span>
+                        <span class="dplayer-label">{{ tran('loop') }}</span>
                         <div class="dplayer-toggle">
                             <input class="dplayer-toggle-setting-input" type="checkbox" name="dplayer-toggle">
                             <label for="dplayer-toggle"></label>
                         </div>
                     </div>
                     <div class="dplayer-setting-item dplayer-setting-showdan">
-                        <span class="dplayer-label">{{ tran('Show danmaku') }}</span>
+                        <span class="dplayer-label">{{ tran('show-danmaku') }}</span>
                         <div class="dplayer-toggle">
                             <input class="dplayer-showdan-setting-input" type="checkbox" name="dplayer-toggle-dan">
                             <label for="dplayer-toggle-dan"></label>
                         </div>
                     </div>
                     <div class="dplayer-setting-item dplayer-setting-danunlimit">
-                        <span class="dplayer-label">{{ tran('Unlimited danmaku') }}</span>
+                        <span class="dplayer-label">{{ tran('unlimited-danmaku') }}</span>
                         <div class="dplayer-toggle">
                             <input class="dplayer-danunlimit-setting-input" type="checkbox" name="dplayer-toggle-danunlimit">
                             <label for="dplayer-toggle-danunlimit"></label>
                         </div>
                     </div>
                     <div class="dplayer-setting-item dplayer-setting-danmaku">
-                        <span class="dplayer-label">{{ tran('Opacity for danmaku') }}</span>
+                        <span class="dplayer-label">{{ tran('opacity-danmaku') }}</span>
                         <div class="dplayer-danmaku-bar-wrap">
                             <div class="dplayer-danmaku-bar">
                                 <div class="dplayer-danmaku-bar-inner">
@@ -182,17 +182,17 @@
                 <div class="dplayer-setting-speed-panel">
                     {{ each options.playbackSpeed }}
                         <div class="dplayer-setting-speed-item" data-speed="{{ $value }}">
-                            <span class="dplayer-label">{{ $value === 1 ? tran('Normal') : $value }}</span>
+                            <span class="dplayer-label">{{ $value === 1 ? tran('normal') : $value }}</span>
                         </div>
                     {{ /each }}
                 </div>
             </div>
         </div>
         <div class="dplayer-full">
-            <button class="dplayer-icon dplayer-full-in-icon" data-balloon="{{ tran('Web full screen') }}" data-balloon-pos="up">
+            <button class="dplayer-icon dplayer-full-in-icon" data-balloon="{{ tran('web-fullscreen') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.fullWeb }}</span>
             </button>
-            <button class="dplayer-icon dplayer-full-icon" data-balloon="{{ tran('Full screen') }}" data-balloon-pos="up">
+            <button class="dplayer-icon dplayer-full-icon" data-balloon="{{ tran('fullscreen') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.full }}</span>
             </button>
         </div>
@@ -252,7 +252,7 @@
 <div class="dplayer-menu">
     {{ each options.contextmenu }}
         <div class="dplayer-menu-item">
-            <a{{ if $value.link }} target="_blank"{{ /if }} href="{{ $value.link || 'javascript:void(0);' }}">{{ tran($value.text) }}</a>
+            <a{{ if $value.link }} target="_blank"{{ /if }} href="{{ $value.link || 'javascript:void(0);' }}">{{ if $value.key }} {{ tran($value.key) }}{{ /if }}{{ if $value.text }} {{$value.text}}{{ /if }}</a>
         </div>
     {{ /each }}
 </div>