feat: Add translation populating procedure for the web interface.

This commit is contained in:
Terry Geng 2020-07-12 14:29:50 +08:00
parent 9f2c4a2afd
commit 97309599f1
No known key found for this signature in database
GPG Key ID: F982F8EA1DF720E7
12 changed files with 239 additions and 110 deletions

View File

@ -7,17 +7,18 @@ steps:
image: node image: node
commands: commands:
- (cd web && npm install && npm run build) - (cd web && npm install && npm run build)
- (cd templates/ && ./translate.py)
- name: deploy-testing - name: deploy-testing
image: debian image: debian
commands: commands:
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- apt-get -qq update && apt-get -qq install git > /dev/null - apt-get -qq update && apt-get -qq install git > /dev/null
- sed -i 's/target_version = git/target_version = testing/' configuration.default.ini - sed -i 's/target_version = git/target_version = testing/' configuration.default.ini
- version=$(git rev-parse HEAD) - version=$(git rev-parse HEAD)
- echo "current git commit is $version" - echo "current git commit is $version"
- echo $version > /mnt/botamusique/testing-version - echo $version > /mnt/botamusique/testing-version
- sed -i "s/version = 'git'/version = '$version'/" mumbleBot.py - sed -i "s/version = 'git'/version = '$version'/" mumbleBot.py
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- rm -rf .git* - rm -rf .git*
- rm -rf web - rm -rf web
- mkdir /tmp/botamusique - mkdir /tmp/botamusique
@ -51,6 +52,7 @@ steps:
- name: deploy-stable - name: deploy-stable
image: debian image: debian
commands: commands:
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- apt-get -qq update && apt-get -qq install jq curl git pandoc > /dev/null - apt-get -qq update && apt-get -qq install jq curl git pandoc > /dev/null
- sed -i 's/target_version = git/target_version = stable/' configuration.default.ini - sed -i 's/target_version = git/target_version = stable/' configuration.default.ini
- git fetch --tags - git fetch --tags
@ -59,7 +61,6 @@ steps:
- echo $version > /mnt/botamusique/version - echo $version > /mnt/botamusique/version
- sed -i "s/version = 'git'/version = '$version'/" mumbleBot.py - sed -i "s/version = 'git'/version = '$version'/" mumbleBot.py
- curl --silent "https://api.github.com/repos/azlux/botamusique/releases/latest" | jq -r '.body' | pandoc --from gfm --to html - --output - > /mnt/botamusique/changelog - curl --silent "https://api.github.com/repos/azlux/botamusique/releases/latest" | jq -r '.body' | pandoc --from gfm --to html - --output - > /mnt/botamusique/changelog
- (cd lang && ./sync_translation.py --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push)
- rm -rf .git* - rm -rf .git*
- rm -rf web - rm -rf web
- mkdir /tmp/botamusique - mkdir /tmp/botamusique

2
.gitignore vendored
View File

@ -116,7 +116,7 @@ tmp/
*.db *.db
templates/* templates/*.html
# Pycharm # Pycharm
.idea/ .idea/

View File

@ -91,9 +91,8 @@ cd botamusique
python3 -m venv venv python3 -m venv venv
venv/bin/pip install wheel venv/bin/pip install wheel
venv/bin/pip install -r requirements.txt venv/bin/pip install -r requirements.txt
cd web (cd web && npm install && npm run build)
npm install (cd templates/ && ./translate.py)
npm run build
``` ```
</details> </details>

View File

@ -169,7 +169,7 @@ def requires_auth(f):
bad_access_count[request.remote_addr] = 1 bad_access_count[request.remote_addr] = 1
log.info(f"web: bad token from ip {request.remote_addr}.") log.info(f"web: bad token from ip {request.remote_addr}.")
return render_template('need_token.html', return render_template(f'need_token.{var.language}.html',
name=var.config.get('bot', 'username'), name=var.config.get('bot', 'username'),
command=f"{var.config.get('commands', 'command_symbol')[0]}{var.config.get('commands', 'requests_webinterface_access')}") command=f"{var.config.get('commands', 'command_symbol')[0]}{var.config.get('commands', 'requests_webinterface_access')}")
@ -225,7 +225,7 @@ def get_all_dirs():
@web.route("/", methods=['GET']) @web.route("/", methods=['GET'])
@requires_auth @requires_auth
def index(): def index():
return open('templates/index.html', "r").read() return open(f"templates/index.{var.language}.html", "r").read()
@web.route("/playlist", methods=['GET']) @web.route("/playlist", methods=['GET'])
@ -554,7 +554,6 @@ def library_info():
tags = var.music_db.query_all_tags() tags = var.music_db.query_all_tags()
max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB")) max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB"))
print(get_all_dirs())
return jsonify(dict( return jsonify(dict(
dirs=get_all_dirs(), dirs=get_all_dirs(),
upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True), upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True),

View File

@ -76,5 +76,82 @@
"yt_no_more": "No more results!", "yt_no_more": "No more results!",
"yt_query_error": "Unable to query youtube!", "yt_query_error": "Unable to query youtube!",
"yt_result": "Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />\n<i>!ytquery -n</i> for the next page." "yt_result": "Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />\n<i>!ytquery -n</i> for the next page."
},
"web": {
"action": "Action",
"add": "Add",
"add_all": "Add All",
"add_radio": "Add Radio",
"add_radio_url": "Add Radio URL",
"add_to_bottom": "Add to bottom",
"add_to_bottom_of_current_playlist": "Add to bottom of current playlist",
"add_to_playlist_next": "Add to playlist right after current song",
"add_url": "Add URL",
"add_youtube_or_soundcloud_url": "Add Youtube or Soundcloud URL",
"are_you_really_sure": "Are you really sure?",
"aria_botamusique_logo": "Botamusique Logo: a person with two headphones, enjoying the music",
"aria_default_cover": "A black square with two eight notes beamed together.",
"aria_empty_box": "A drawing of an empty box.",
"aria_remove_this_song": "Remove this song from the current playlist",
"aria_skip_current_song": "Skip current song and play this song right now",
"aria_skip_to_next_track": "Skip to next track",
"aria_spinner": "A loading spinner",
"aria_warning_of_deletion": "Warning about deletion of files.",
"autoplay": "Autoplay",
"browse_music_file": "Browse Music file",
"cancel": "Cancel",
"cancel_upload_warning": "<strong>Are you really sure?</strong> <br /> Click again to abort uploading.",
"change_playback_mode": "Change Playback Mode",
"choose_file": "Choose file",
"clear_playlist": "Clear Playlist",
"close": "Close",
"delete_all": "Delete All",
"delete_all_files": "Delete All Listed Files",
"delete_file_warning": "All files listed here, include files on other pages, will be deleted from your hard-drive.\n Is that what you want?",
"directory": "Directory",
"download_all": "Download All",
"download_song_from_library": "Download song from library",
"edit_submit": "Edit!",
"edit_tags_for": "Edit tags for",
"expand_playlist": "See item <span\n class=\"playlist-expand-item-range\"></span> on the playlist.",
"file": "File",
"filters": "Filters",
"index": "#",
"keywords": "Keywords",
"keywords_placeholder": "Keywords...",
"mini_player_title": "Now Playing...",
"music_library": "Music Library",
"next_to_play": "Next to play",
"no_tag": "No tag",
"oneshot": "One-shot",
"open_volume_controls": "Open Volume Controls",
"page_title": "botamusique Web Interface",
"pause": "Pause",
"play": "Play",
"playlist_controls": "Playlist controls",
"radio": "Radio",
"radio_url_placeholder": "Radio URL...",
"random": "Random",
"remove_song_from_library": "Remove song from library",
"repeat": "Repeat",
"rescan_files": "Rescan Files",
"skip_track": "Skip Track",
"submit": "Submit",
"tags": "Tags",
"tags_to_add": "Tags to add",
"title": "Title",
"token": "Token",
"token_required": "Token Required",
"token_required_message": "You are accessing the web interface of {{ name }}.\nA token is needed to grant you access.<br />\nPlease send \"{{ command }}\" to the bot in mumble to acquire one.",
"type": "Type",
"upload_file": "Upload File",
"upload_submit": "Upload!",
"upload_to": "Upload To",
"uploaded_finished": "Uploaded finished!",
"uploading_files": "Uploading files...",
"url": "URL",
"url_path": "Url/Path",
"url_placeholder": "URL...",
"volume_slider": "Volume Slider"
} }
} }

View File

@ -832,6 +832,7 @@ if __name__ == '__main__':
if lang not in supported_languages: if lang not in supported_languages:
raise KeyError(f"Unsupported language {lang}") raise KeyError(f"Unsupported language {lang}")
var.language = lang
constants.load_lang(lang) constants.load_lang(lang)
# ====================== # ======================

58
templates/translate.py Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env python3
import os
import json
import re
import jinja2
default_lang_dict = {}
lang_dict = {}
def load_lang(lang):
with open(f"../lang/{lang}.json", "r") as f:
return json.load(f)
def tr(option):
try:
if option in lang_dict['web'] and lang_dict['web'][option]:
string = lang_dict['web'][option]
else:
string = default_lang_dict['web'][option]
return string
except KeyError:
raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
if __name__ == "__main__":
html_files = os.listdir('.')
for html_file in html_files:
match = re.search("(.+)\.template\.html", html_file)
if match is None:
continue
print(f"Populating {html_file} with translations...")
basename = match[1]
with open(html_file, "r") as f:
html = f.read()
lang_files = os.listdir('../lang')
lang_list = []
default_lang_dict = load_lang("en_US")
for lang_file in lang_files:
match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
if match:
lang_list.append(match[1])
template = jinja2.Template(html)
for lang in lang_list:
print(f" - Populating {lang}...")
lang_dict = load_lang(lang)
with open(f"{basename}.{lang}.html", "w") as f:
f.write(template.render(tr=tr))
print("Done.")

View File

@ -23,3 +23,5 @@ bot_logger = None
music_folder = "" music_folder = ""
tmp_folder = "" tmp_folder = ""
language = ""

View File

@ -816,7 +816,7 @@ const modal_tag = $('.modal-tag');
const modal_tag_text = $('.modal-tag-text'); const modal_tag_text = $('.modal-tag-text');
function addTagModalShow(_id, _title, _tag_tuples) { function addTagModalShow(_id, _title, _tag_tuples) {
add_tag_modal_title.html('Edit tags for ' + _title); add_tag_modal_title.html(_title);
add_tag_modal_item_id.val(_id); add_tag_modal_item_id.val(_id);
add_tag_modal_tags.empty(); add_tag_modal_tags.empty();
_tag_tuples.forEach(function (tag_tuple) { _tag_tuples.forEach(function (tag_tuple) {

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>botamusique web interface</title> <title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" /> <link rel="icon" href="static/image/favicon.ico" />
@ -18,46 +18,46 @@
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<img src="static/image/logo.png" height="200px" <img src="static/image/logo.png" height="200px"
alt="Botamusique Logo: a person with two headphones, enjoying the music"> alt="{{ tr('aria_botamusique_logo') }}">
</div> </div>
<div class="col my-auto"> <div class="col my-auto">
<h1>botamusique Web Interface</h1> <h1>{{ tr('page_title') }}</h1>
</div> </div>
</div> </div>
</header> </header>
<main id="playlist" class="container mb-5"> <main id="playlist" class="container mb-5">
<div class="btn-toolbar mb-2" role="toolbar" aria-label="Playlist controls"> <div class="btn-toolbar mb-2" role="toolbar" aria-label="{{ tr('playlist_controls') }}">
<button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="Play"> <button type="button" id="play-pause-btn" class="btn btn-info mb-2 btn-space" aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="Skip Track"> <button type="button" id="fast-forward-btn" class="btn btn-info mb-2" aria-label="{{ tr('skip_track') }}">
<i class="fas fa-fast-forward"></i> <i class="fas fa-fast-forward"></i>
</button> </button>
<div class="ml-auto"> <div class="ml-auto">
<div class="dropdown mr-2"> <div class="dropdown mr-2">
<button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode" <button class="btn btn-secondary dropdown-toggle" type="button" id="play-mode"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="Change Playback Mode"> aria-label="{{ tr('change_playback_mode') }}">
<i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i> <i class="fas fa-tasks mr-2" aria-hidden="true" id="modeIndicator"></i>
</button> </button>
<div class="dropdown-menu" aria-labelledby="play-mode"> <div class="dropdown-menu" aria-labelledby="play-mode">
<a class="dropdown-item" href="#" id="one-shot-mode-btn"> <a class="dropdown-item" href="#" id="one-shot-mode-btn">
<i class="fas fa-tasks mr-2" aria-hidden="true"></i>One-shot <i class="fas fa-tasks mr-2" aria-hidden="true"></i> {{ tr('oneshot') }}
</a> </a>
<a class="dropdown-item" href="#" id="random-mode-btn"> <a class="dropdown-item" href="#" id="random-mode-btn">
<i class="fas fa-random mr-2" aria-hidden="true"></i>Random <i class="fas fa-random mr-2" aria-hidden="true"></i> {{ tr('random') }}
</a> </a>
<a class="dropdown-item" href="#" id="repeat-mode-btn"> <a class="dropdown-item" href="#" id="repeat-mode-btn">
<i class="fas fa-redo mr-2" aria-hidden="true"></i>Repeat <i class="fas fa-redo mr-2" aria-hidden="true"></i> {{ tr('repeat') }}
</a> </a>
<a class="dropdown-item" href="#" id="autoplay-mode-btn"> <a class="dropdown-item" href="#" id="autoplay-mode-btn">
<i class="fas fa-robot mr-2" aria-hidden="true"></i>Autoplay <i class="fas fa-robot mr-2" aria-hidden="true"></i> {{ tr('autoplay') }}
</a> </a>
</div> </div>
</div> </div>
<button type="button" id="volume-popover-btn" class="btn btn-warning ml-1" <button type="button" id="volume-popover-btn" class="btn btn-warning ml-1"
aria-label="Open Volume Controls"> aria-label="{{ tr('open_volume_controls') }}">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
</button> </button>
@ -67,7 +67,7 @@
</a> </a>
<input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01" <input type="range" class="custom-range ml-1" id="volume-slider" min="0" max="1" step="0.01"
value="0.5" aria-label="Volume Slider" /> value="0.5" aria-label="{{ tr('volume_slider') }}" />
<a id="volume-up-btn"> <a id="volume-up-btn">
<i class="fa fa-volume-up" aria-hidden="true"></i> <i class="fa fa-volume-up" aria-hidden="true"></i>
@ -81,29 +81,28 @@
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col" class="d-none d-md-table-cell">#</th> <th scope="col" class="d-none d-md-table-cell">{{ tr('index') }}</th>
<th scope="col" class="w-50">Title</th> <th scope="col" class="w-50">{{ tr('title') }}</th>
<th scope="col" class="d-none d-md-table-cell">Url/Path</th> <th scope="col" class="d-none d-md-table-cell">{{ tr('url_path') }}</th>
<th scope="col">Action</th> <th scope="col">{{ tr('action') }}</th>
</tr> </tr>
</thead> </thead>
<tbody id="playlist-table" class="playlist-table"> <tbody id="playlist-table" class="playlist-table">
<tr id="playlist-loading"> <tr id="playlist-loading">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg" <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="A loading spinner" /> alt="{{ tr('aria_spinner') }}" />
</td> </td>
</tr> </tr>
<tr id="playlist-empty" class="d-none"> <tr id="playlist-empty" class="d-none">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="A drawing of an empty box." /> alt="{{ tr('aria_empty_box') }}" />
</td> </td>
</tr> </tr>
<tr class="playlist-expand table-dark d-none"> <tr class="playlist-expand table-dark d-none">
<td colspan="4" class="text-center"> <td colspan="4" class="text-center">
<a class="text-muted" href="javascript:">See item <span <a class="text-muted" href="javascript:">{{ tr('expand_playlist') }}</a>
class="playlist-expand-item-range"></span> on the playlist.</a>
</td> </td>
</tr> </tr>
<tr class="playlist-item-template d-none" aria-hidden="true"> <tr class="playlist-item-template d-none" aria-hidden="true">
@ -112,7 +111,7 @@
<input hidden type="hidden" class="playlist-item-id" value="" /> <input hidden type="hidden" class="playlist-item-id" value="" />
<div class="float-left"> <div class="float-left">
<img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png" <img width="80" class="playlist-item-thumbnail" src="static/image/unknown-album.png"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
</div> </div>
<div class="playlist-artwork"> <div class="playlist-artwork">
<b class="playlist-item-title"></b> <b class="playlist-item-title"></b>
@ -134,11 +133,11 @@
<td> <td>
<div class="btn-group"> <div class="btn-group">
<button type="button" class="playlist-item-play btn btn-info btn-sm" <button type="button" class="playlist-item-play btn btn-info btn-sm"
aria-label="Skip current song and play this song right now"> aria-label="{{ tr('aria_skip_current_song') }}">
<i class="fas fa-play" aria-hidden="true"></i> <i class="fas fa-play" aria-hidden="true"></i>
</button> </button>
<button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1" <button type="button" class="playlist-item-trash btn btn-danger btn-sm ml-1"
aria-label="Remove this song from the current playlist"> aria-label="{{ tr('aria_remove_this_song') }}">
<i class="fas fa-trash-alt" aria-hidden="true"></i> <i class="fas fa-trash-alt" aria-hidden="true"></i>
</button> </button>
</div> </div>
@ -150,52 +149,51 @@
<div class="btn-group"> <div class="btn-group">
<button type="button" id="clear-playlist-btn" class="btn btn-danger mr-1"> <button type="button" id="clear-playlist-btn" class="btn btn-danger mr-1">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Clear Playlist <i class="fas fa-trash-alt" aria-hidden="true"></i> {{ tr('clear_playlist') }}</button>
</button>
</div> </div>
</main> </main>
<div class="container mb-3"> <div class="container mb-3">
<h2 id="forms">Music Library</h2> <h2 id="forms">{{ tr('music_library') }}</h2>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Filters</h3> <h3 class="card-title">{{ tr('filters') }}</h3>
<hr> <hr>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<fieldset id="filter-type" class="mb-2"> <fieldset id="filter-type" class="mb-2">
<legend>Type</legend> <legend>{{ tr('type') }}</legend>
<div class="btn-group btn-group-sm btn-group-toggle"> <div class="btn-group btn-group-sm btn-group-toggle">
<label id="filter-type-file" class="btn btn-secondary"> <label id="filter-type-file" class="btn btn-secondary">
<input type="checkbox" name="options"> File <input type="checkbox" name="options">{{ tr('file') }}
</label> </label>
<label id="filter-type-url" class="btn btn-secondary"> <label id="filter-type-url" class="btn btn-secondary">
<input type="checkbox" name="options"> URL <input type="checkbox" name="options">{{ tr('url') }}
</label> </label>
<label id="filter-type-radio" class="btn btn-secondary"> <label id="filter-type-radio" class="btn btn-secondary">
<input type="checkbox" name="options"> Radio <input type="checkbox" name="options">{{ tr('radio') }}
</label> </label>
</div> </div>
</fieldset> </fieldset>
<label for="filter-dir">Directory</label> <label for="filter-dir">{{ tr('directory') }}</label>
<div id="filter-path" class="input-group mb-2"> <div id="filter-path" class="input-group mb-2">
<select class="form-control form-control-sm" id="filter-dir" disabled> <select class="form-control form-control-sm" id="filter-dir" disabled>
<option value="">.</option> <option value="">.</option>
</select> </select>
</div> </div>
<label for="filter-keywords">Keywords</label> <label for="filter-keywords">{{ tr('keywords') }}</label>
<div id="filter-path" class="input-group mb-2"> <div id="filter-path" class="input-group mb-2">
<input class="form-control form-control-sm" id="filter-keywords" name="keywords" <input class="form-control form-control-sm" id="filter-keywords" name="keywords"
placeholder="Keywords..." style="margin-top:5px;" /> placeholder="{{ tr('keywords_placeholder') }}" style="margin-top:5px;" />
</div> </div>
</div> </div>
<div class="col"> <div class="col">
<fieldset id="filter-tags"> <fieldset id="filter-tags">
<legend>Tags</legend> <legend>{{ tr('tags') }}</legend>
<span class="filter-tag tag-unclicked tag-click badge"></span> <span class="filter-tag tag-unclicked tag-click badge"></span>
</fieldset> </fieldset>
</div> </div>
@ -205,11 +203,11 @@
<div id="library-group" class="list-group library-group" style="overflow: auto;"> <div id="library-group" class="list-group library-group" style="overflow: auto;">
<div id="library-item-loading" class="list-group-item library-item"> <div id="library-item-loading" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/loading.svg" <img style="margin: auto; width: 35px;" src="static/image/loading.svg"
alt="A loading spinner" /> alt="{{ tr('aria_spinner') }}" />
</div> </div>
<div id="library-item-empty" style="display: none" class="list-group-item library-item"> <div id="library-item-empty" style="display: none" class="list-group-item library-item">
<img style="margin: auto; width: 35px;" src="static/image/empty_box.svg" <img style="margin: auto; width: 35px;" src="static/image/empty_box.svg"
alt="A drawing of an empty box." /> alt="{{ tr('aria_empty_box') }}" />
</div> </div>
<div id="library-item" style="display: none;" class="list-group-item library-item"> <div id="library-item" style="display: none;" class="list-group-item library-item">
<input hidden type="hidden" class="library-item-id" value="" /> <input hidden type="hidden" class="library-item-id" value="" />
@ -217,10 +215,10 @@
<div class="library-thumb-col"> <div class="library-thumb-col">
<div class="library-thumb-img"> <div class="library-thumb-img">
<img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png" <img class="library-item-thumb library-thumb-img" src="static/image/unknown-album.png"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
</div> </div>
<div class="btn-group-vertical library-thumb-grp"> <div class="btn-group-vertical library-thumb-grp">
<div class="library-item-play btn btn-secondary library-thumb-btn-up" title="Play"> <div class="library-item-play btn btn-secondary library-thumb-btn-up" title="{{ tr('play') }}">
<i class="fas fa-play" aria-hidden="true"></i> <i class="fas fa-play" aria-hidden="true"></i>
</div> </div>
</div> </div>
@ -239,14 +237,14 @@
<div class="library-item-tags"> <div class="library-item-tags">
<a class="tag-space tag-click library-item-edit"><i class="fas fa-edit" <a class="tag-space tag-click library-item-edit"><i class="fas fa-edit"
style="color: #AAAAAA"></i></a> style="color: #AAAAAA"></i></a>
<span class="library-item-notag badge badge-light text-muted font-italic">No tag</span> <span class="library-item-notag badge badge-light text-muted font-italic">{{ tr('no_tag') }}</span>
<span class="library-item-tag tag-space badge">Tag</span> <span class="library-item-tag tag-space badge">Tag</span>
</div> </div>
</div> </div>
<div class="btn-group library-action"> <div class="btn-group library-action">
<button class="library-item-add-next btn btn-info btn-sm btn-space" type="button" <button class="library-item-add-next btn btn-info btn-sm btn-space" type="button"
title="Next to play" aria-label="Add to playlist right after current song"> title="{{ tr('next_to_play') }}" aria-label="{{ tr('add_to_playlist_next') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="5 5 17 17"> viewBox="5 5 17 17">
<path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z"> <path d="m5.700245,3.92964l0,14.150376l11.451127,-7.075188l-11.451127,-7.075188z">
@ -257,7 +255,7 @@
</svg> </svg>
</button> </button>
<button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space" <button class="library-item-add-bottom library-btn btn btn-info btn-sm btn-space"
type="button" title="Add to bottom" aria-label="Add to bottom of current playlist"> type="button" title="{{ tr('add_to_bottom') }}" aria-label="{{ tr('add_to_bottom_of_current_playlist') }}">
<svg class="library-btn-svg" style="width: 1rem; fill: currentColor;" <svg class="library-btn-svg" style="width: 1rem; fill: currentColor;"
viewBox="2 2 20 20"> viewBox="2 2 20 20">
<path <path
@ -266,11 +264,11 @@
</svg> </svg>
</button> </button>
<button class="library-item-download btn btn-primary btn-sm btn-space" type="button" <button class="library-item-download btn btn-primary btn-sm btn-space" type="button"
aria-label="Download song from library"> aria-label="{{ tr('download_song_from_library') }}">
<i class="fas fa-download" aria-hidden="true"></i> <i class="fas fa-download" aria-hidden="true"></i>
</button> </button>
<button class="library-item-trash btn btn-danger btn-sm btn-space" type="button" <button class="library-item-trash btn btn-danger btn-sm btn-space" type="button"
aria-label="Remove song from library"> aria-label="{{ tr('remove_song_from_library') }}">
<i class="fas fa-trash-alt"></i> <i class="fas fa-trash-alt"></i>
</button> </button>
</div> </div>
@ -290,40 +288,36 @@
<div class="btn-group mb-2" role="group"> <div class="btn-group mb-2" role="group">
<button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1"> <button id="add-to-playlist-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-plus" aria-hidden="true"></i> Add All <i class="fas fa-plus" aria-hidden="true"></i>{{ tr('add_all') }}
</button> </button>
<button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1"> <button id="library-rescan-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-sync-alt" aria-hidden="true"></i> Rescan Files <i class="fas fa-sync-alt" aria-hidden="true"></i>{{ tr('rescan_files') }}
</button> </button>
<button id="library-download-btn" type="button" class="btn btn-secondary mr-1"> <button id="library-download-btn" type="button" class="btn btn-secondary mr-1">
<i class="fas fa-download" aria-hidden="true"></i> Download All <i class="fas fa-download" aria-hidden="true"></i>{{ tr('download_all') }}
</button> </button>
<button type="button" class="btn btn-danger mr-1" data-toggle="modal" <button type="button" class="btn btn-danger mr-1" data-toggle="modal"
data-target="#deleteWarningModal"> data-target="#deleteWarningModal">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Delete All <i class="fas fa-trash-alt" aria-hidden="true"></i>{{ tr('delete_all') }}
</button> </button>
</div> </div>
<!-- QUESTION: should this div have aria-hidden as true?? -->
<div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog" <div class="modal fade" id="deleteWarningModal" tabindex="-1" role="dialog"
aria-label="Modal Window for warning about deletion of files." aria-hidden="true"> aria-label="{{ tr('aria_warning_of_deletion') }}" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="deleteWarningModalLabel">Are you really sure?</h3> <h3 class="modal-title" id="deleteWarningModalLabel">{{ tr('are_you_really_sure') }}</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="{{ tr('close') }}">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
All files listed here, include files on other pages, will be deleted from your {{ tr('delete_file_warning') }}</div>
hard-drive.
Is that what you want?
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="library-delete-btn" type="button" class="btn btn-danger" <button id="library-delete-btn" type="button" class="btn btn-danger"
data-dismiss="modal">Delete All Listed Files</button> data-dismiss="modal">{{ tr('delete_all_files') }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -336,7 +330,7 @@
<input type="hidden" id="uploadDisabled" value="false" /> <input type="hidden" id="uploadDisabled" value="false" />
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Upload File</h3> <h3 class="card-title">{{ tr('upload_file') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<form action="./upload" method="post" enctype="multipart/form-data"> <form action="./upload" method="post" enctype="multipart/form-data">
@ -345,8 +339,8 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<div id="uploadField" style="display: flex; width: 100%"> <div id="uploadField" style="display: flex; width: 100%">
<div class="custom-file"> <div class="custom-file">
<input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="Browse Music file" multiple /> <input type="file" name="file[]" class="custom-file-input" id="uploadSelectFile" aria-describedby="uploadSubmit" value="{{ tr('browse_music_file') }}" multiple />
<label class="custom-file-label" for="uploadSelectFile">Choose file</label> <label class="custom-file-label" for="uploadSelectFile">{{ tr('choose_file') }}</label>
</div> </div>
</div> </div>
</div> </div>
@ -356,7 +350,7 @@
<div class="col"> <div class="col">
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="input-group-prepend"> <div class="input-group-prepend">
<label for="uploadTargetDir" class="input-group-text">Upload To</label> <label for="uploadTargetDir" class="input-group-text">{{ tr('upload_to') }}</label>
</div> </div>
<input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" /> <input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" />
<datalist id="upload-target-dirs"> <datalist id="upload-target-dirs">
@ -364,7 +358,7 @@
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>Upload!</button> <button class="btn btn-primary" type="button" id="uploadSubmit"><i class="fas fa-upload mr-1"></i>{{ tr('upload_submit') }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -378,29 +372,29 @@
<div class="card-deck"> <div class="card-deck">
<div id="add-music-url" class="card"> <div id="add-music-url" class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Add URL</h3> <h3 class="card-title">{{ tr('add_url') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<label for="music-url-input">Add Youtube or Soundcloud URL</label> <label for="music-url-input">{{ tr('add_youtube_or_soundcloud_url') }}</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input class="form-control" type="text" id="music-url-input" placeholder="URL..."> <input class="form-control" type="text" id="music-url-input" placeholder="{{ tr('url_placeholder') }}">
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Add URL {{ tr('add_url') }}
</button> </button>
</div> </div>
</div> </div>
<div id="add-radio-url" class="card"> <div id="add-radio-url" class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Add Radio</h3> <h3 class="card-title">{{ tr('add_radio') }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<label for="radio-url-input">Add Radio URL</label> <label for="radio-url-input">{{ tr('add_radio_url') }}</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="radio-url-input" class="form-control" type="text" placeholder="Radio Address..."> <input id="radio-url-input" class="form-control" type="text" placeholder="{{ tr('radio_url_placeholder') }}">
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
Add Radio {{ tr('add_radio') }}
</button> </button>
</div> </div>
</div> </div>
@ -418,26 +412,26 @@
<div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false"> <div id="playerToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header"> <div class="toast-header">
<i class="fas fa-play-circle mr-2 text-primary"></i> <i class="fas fa-play-circle mr-2 text-primary"></i>
<strong class="mr-auto">Now Playing...</strong> <strong class="mr-auto">{{ tr('mini_player_title') }}</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="toast-body" id="playerContainer"> <div class="toast-body" id="playerContainer">
<img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="A drawing of an empty box." /> <img id="playerArtworkIdle" src="static/image/empty_box.svg" alt="{{ tr('aria_empty_box') }}" />
<img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;" <img id="playerArtwork" src="static/image/unknown-album.png" style="display: none;"
alt="A black square with two eight notes beamed together." /> alt="{{ tr('aria_default_cover') }}" />
<div id="playerInfo"> <div id="playerInfo">
<div id="playerActionBox"> <div id="playerActionBox">
<button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none" <button id="playerPlayBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="Play"> aria-label="{{ tr('play') }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none" <button id="playerPauseBtn" class="btn btn-primary btn-sm btn-space" style="display: none"
aria-label="Pause"> aria-label="{{ tr('pause') }}">
<i class="fas fa-pause"></i> <i class="fas fa-pause"></i>
</button> </button>
<button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="Skip to next track"> <button id="playerSkipBtn" class="btn btn-primary btn-sm" aria-label="{{ tr('aria_skip_to_next_track') }}">
<i class="fas fa-fast-forward"></i> <i class="fas fa-fast-forward"></i>
</button> </button>
</div> </div>
@ -470,7 +464,7 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="addTagModalTitle">Edit tags for ?</h3> <h3 class="modal-title">{{ tr('edit_tags_for') }} <span id="addTagModalTitle">?</span></h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
@ -486,17 +480,17 @@
</div> </div>
<div class="input-group"> <div class="input-group">
<input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput" <input class="form-control form-control-sm btn-space" type="text" id="addTagModalInput"
placeholder="tag1,tag2,..." aria-label="Tags to add"> placeholder="tag1,tag2,..." aria-label="{{ tr('tags_to_add') }}">
<button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm"> <button id="addTagModalAddBtn" type="button" class="btn btn-primary btn-sm">
<i class="fas fa-plus" aria-hidden="true"></i> <i class="fas fa-plus" aria-hidden="true"></i>
Add {{ tr('add') }}
</button> </button>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ tr('close') }}</button>
<button id="addTagModalSubmit" type="button" class="btn btn-success" <button id="addTagModalSubmit" type="button" class="btn btn-success"
data-dismiss="modal">Edit!</button> data-dismiss="modal">{{ tr('edit_submit') }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -507,12 +501,12 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>Uploading files...</h3> <h3 class="modal-title" id="uploadTitle"><i class="fas fa-upload mr-1"></i>{{ tr('uploading_files') }}</h3>
</div> </div>
<div id="uploadModalBody" class="modal-body"> <div id="uploadModalBody" class="modal-body">
<div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none"> <div id="uploadSuccessAlert" class="alert alert-success" role="alert" style="display: none">
<i class="fas fa-check mr-1"></i> <i class="fas fa-check mr-1"></i>
Uploaded finished! {{ tr('uploaded_finished') }}
</div> </div>
<div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;"> <div id="uploadModalList" style="margin-left: 5px; margin-bottom: 10px;">
<div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;"> <div class="uploadItem" style="display: none; width: 100%; padding-bottom: 8px;">
@ -529,11 +523,11 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal"> <button type="button" id="uploadClose" class="btn btn-success" data-dismiss="modal">
<i class="fas fa-times mr-1"></i> Close</button> <i class="fas fa-times mr-1"></i> {{ tr('close') }}</button>
<button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip" <button type="button" id="uploadCancel" class="btn btn-danger" data-toggle="tooltip"
data-html="true" data-html="true"
title="<strong>Are you really sure?</strong> <br /> Click again to abort uploading."> title="{{ tr('cancel_upload_warning') }}">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Cancel</button> <i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> {{ tr('cancel') }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>botamusique web interface</title> <title>{{ tr('page_title') }}</title>
<link rel="icon" href="static/image/favicon.ico" /> <link rel="icon" href="static/image/favicon.ico" />
@ -16,19 +16,17 @@
<div class="col-8" style="margin: auto; padding-top: 50px;"> <div class="col-8" style="margin: auto; padding-top: 50px;">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Token Required {{ tr('token_required') }}
</div> </div>
<div class="card-body"> <div class="card-body">
<h3>Token Required!</h3> <h3>{{ tr('token_required') }}</h3>
You are accessing the web interface of {{ name }}. {{ tr('token_required_message') }}
A token is needed to grant you access.<br />
Please send "{{ command }}" to the bot in mumble to acquire one.
<form action="." method="get"> <form action="." method="get">
<div class="form-group mt-3"> <div class="form-group mt-3">
<label for="token_input">Token</label> <label for="token_input">{{ tr('token') }}</label>
<div class="input-group"> <div class="input-group">
<input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx"> <input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx">
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">{{ tr('submit') }}</button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -29,13 +29,13 @@ module.exports = {
filename: 'static/css/[name].css', filename: 'static/css/[name].css',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'templates/index.html', filename: 'templates/index.template.html',
template: './templates/index.html', template: './templates/index.template.html',
inject: false, inject: false,
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'templates/need_token.html', filename: 'templates/need_token.template.html',
template: './templates/need_token.html', template: './templates/need_token.template.html',
inject: false, inject: false,
}), }),
], ],