diff --git a/.drone.yml b/.drone.yml
index da54c2b..f30258a 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -20,6 +20,7 @@ steps:
- echo "current git commit is $version"
- echo $version > /mnt/botamusique/testing-version
- sed -i "s/version = 'git'/version = '$version'/" mumbleBot.py
+ - ./lang/translate.py
- rm -rf .git*
- rm -rf web
- mkdir /tmp/botamusique
diff --git a/command.py b/command.py
index d55146c..29d4f48 100644
--- a/command.py
+++ b/command.py
@@ -6,7 +6,8 @@ import json
import re
import pymumble_py3 as pymumble
-from constants import tr, commands
+from constants import tr_cli as tr
+from constants import commands
import interface
import media.system
import util
diff --git a/configuration.default.ini b/configuration.default.ini
index 334c47f..fc5c39e 100644
--- a/configuration.default.ini
+++ b/configuration.default.ini
@@ -195,159 +195,3 @@ list_webinterface_user = webuserlist
add_webinterface_user = webuseradd
remove_webinterface_user = webuserdel
change_user_password = password
-
-[strings]
-current_volume = Current volume: {volume}.
-current_ducking_volume = Volume on ducking: {volume}.
-change_volume = Volume set to {volume} by {user}.
-change_ducking_volume = Volume on ducking set to {volume} by {user}.
-bad_command = {command}: command not found.
-bad_parameter = {command}: invalid parameter.
-error_executing_command = {command}: Command failed with error: {error}.
-not_admin = You are not an admin!
-not_playing = Nothing is playing right now.
-no_file = File not found.
-wrong_pattern = Invalid regex: {error}.
-file_added = Added {item}.
-file_deleted = Deleted {item} from the library.
-multiple_file_added = Multiple items added:
-multiple_file_deleted = Multiple items deleted from the library:
-multiple_file_found = Found:
-page_instruction = Page {current}/{total}. Use !{command} {{page}} to navigate.
-records_omitted = ...
-bad_url = Bad URL requested.
-preconfigurated_radio = Preconfigurated Radio available:
-unable_download = Unable to download {item}. Removed from the library.
-unable_play = Unable to play {item}. Removed from the library.
-which_command = Do you mean
{commands}
-multiple_matches = File not found! Possible candidates:
-queue_contents = Items on the playlist:
-queue_empty = Playlist is empty!
-invalid_index = Invalid index {index}. Use '!queue' to see your playlist.
-now_playing = Playing {item}
-radio = Radio
-file = File
-url_from_playlist = URL
-url = URL
-radio_item = {title} from {name} added by {user}
-file_item = {artist} - {title} added by {user}
-url_from_playlist_item = {title} from playlist {playlist} added by {user}
-url_item = {title} added by {user}
-not_in_my_channel = You're not in my channel, command refused!
-pm_not_allowed = Private message aren't allowed.
-too_long = {song} is too long, removed from playlist!
-download_in_progress = Download of {item} in progress...
-removing_item = Removed entry {item} from playlist.
-user_ban = You are banned, not allowed to do that!
-url_ban = This url is banned!
-rb_query_result = This is the result of your query, send !rbplay 'ID' to play a station:
-rb_play_empty = Please specify a radio station ID!
-paused = Music paused.
-stopped = Music stopped.
-cleared = Playlist emptied.
-database_dropped = Database dropped. All records have gone.
-new_version_found =
Update Available!
Version {new_version} of botamusique is available!
- Changelog
{changelog}
Send !update to update!
-update_successful = botamusique v{version} Installed!
- Changelog
{changelog}
Visit our github repo for more details!
-start_updating = Start updating...
-file_missed = Music file '{file}' missed! This item has been removed from the playlist.
-unknown_mode = Unknown playback mode '{mode}'. It should be one of one-shot, repeat, random.
-current_mode = Current playback mode is {mode}.
-change_mode = Playback mode set to {mode} by {user}.
-repeat = Repeat {song} for {n} times.
-yt_result = Youtube query result: {result_table} Use !sl {{indexes}} to play the item you want.
- !ytquery -n for the next page.
-yt_no_more = No more results!
-yt_query_error = Unable to query youtube!
-playlist_fetching_failed = Unable to fetch the playlist!
-cache_refreshed = Cache refreshed!
-added_tags = Added tags {tags} to {song}.
-added_tags_to_all = Added tags {tags} to songs on the playlist.
-removed_tags = Removed tags {tags} from {song}.
-removed_tags_from_all = Removed tags {tags} from songs on the playlist.
-cleared_tags = Removed all tags from {song}.
-cleared_tags_from_all = Removed all tags from songs on the playlist.
-shortlist_instruction = Use !sl {indexes} to play the item you want.
-auto_paused = Use !play to resume music!
-webpage_address= Your own address to access the web interface is {address}
-web_user_list = Following users has the privilege to access the web interface:
{users}
-user_password_set = Your password has been updated.
-command_disabled = {command}: command disabled!
-
-help = Commands
- Control
-
- - !web - get the URL of the web interface, if enabled.
- - !play (or !p) [{num}] [{start_from}] - resume from pausing / start to play (the num-th song is num if given)
- - !pause - pause
- - !stop - stop playing
- - !skip - jump to the next song
- - !last - jump to the last song
- - !volume {volume} - get or change the volume (from 0 to 100)
- - !mode [{mode}] - get or set the playback mode, {mode} should be one of one-shot (remove
- item once played), repeat (looping through the playlist), random (randomize the playlist),
- autoplay (randomly grab something from the music library).
- - !duck on/off - enable or disable ducking function
- - !duckv - set the volume of the bot when ducking is activated
- - !duckthres - set the threshold of volume to activate ducking (3000 by default)
- - !oust - stop playing and go to default channel
-
- Playlist
-
- - !now (or !np) - display the current song
- - !queue - display items in the playlist
- - !tag {tags} - add all items with tags {tags}, tags separated by ",".
- - !file (or !f) {path/folder/keyword} - add a single file to the playlist by its path or keyword in its path.
- - !filematch (or !fm) {pattern} - add all files that match regex {pattern}
- - !url {url} - add Youtube or SoundCloud music
- - !playlist {url} [{offset}] - add all items in a Youtube or SoundCloud playlist, and start with the {offset}-th item
- - !radio {url} - append a radio {url} to the playlist
- - !rbquery {keyword} - query http://www.radio-browser.info for a radio station
- - !rbplay {id} - play a radio station with {id} (eg. !rbplay 96746)
- - !ysearch {keywords} - query youtube. Use !ysearch -n to turn the page.
- - !yplay {keywords} - add the first search result of {keywords} into the playlist.
- - !shortlist (or !sl) {indexes/*} - add {indexes}-th item (or all items if * is given) on the shortlist.
- - !rm {num} - remove the num-th song on the playlist
- - !repeat [{num}] - repeat current song {num} (1 by default) times.
- - !random - randomize the playlist.
-
- Music Library
-
- - !search {keywords} - find item with {keywords} in the music library, keywords separated by space.
- - !listfile [{pattern}] - display list of available files (whose paths match the regex pattern if {pattern} is given)
- - !addtag [{index}] {tags} - add {tags} to {index}-th(current song if {index} is omitted) item on the playlist, tags separated by ",".
- - !addtag * {tags} - add {tags} to all items on the playlist.
- - !untag [{index/*}] {tags}/* - remove {tags}/all tags from {index}-th(current song if {index} is omitted) item on the playlist.
- - !findtagged (or !ft) {tags} - find item with {tags} in the music library.
- - !delete {index} - delete {index}-th item on the shortlist from the music library.
-
- Other
-
- - !joinme {token} - join your own channel with {token}.
- - !password {password} - change your password, used to access the web interface.
-
-
-admin_help = Admin command
- Bot
-
- - !kill - kill the bot
- - !update - update the bot
- - !userban {user} - ban a user
- - !userunban {user} - unban a user
- - !urlbanlist - list banned url
- - !urlban [{url}] - ban {url} (or current item's url by default) and remove this url from the library.
- - !urlunban {url} - unban {url}
- - !rescan {url} - rebuild local music file cache
- - !dropdatabase - clear the entire database, you will lose all settings and music library.
-
- Web Interface
-
- - !webuserlist - list all users that have the permission of accessing the web interface, if auth mode is 'password'.
- - !webuseradd {nick name} - grant the user with {nick name} the access to the web interface, if auth mode is 'password'.
- - !webuserdel {nick name} - revoke the access to the web interface of {nick name}, if auth mode is 'password'.
- - !update - update the bot
- - !userban {user} - ban a user
-
-
-
diff --git a/configuration.example.ini b/configuration.example.ini
index 79142e4..13c546c 100644
--- a/configuration.example.ini
+++ b/configuration.example.ini
@@ -177,18 +177,3 @@ port = 64738
# see configuration.default.ini. Copy options you want to edit into this file.
#play_file = file, f
#play_file_match = filematch, fm
-
-# [strings] is used to compose what the bot says. You can customize them to fit in
-# the style of your channel, or translate into your own language.
-# For a full list of strings, please see configuration.default.ini.
-# Copy options you want to edit into this file.
-# Note: please keep those {placeholder} of each string in your new string.
-[strings]
-# Some examples are:
-#current_volume = Current volume: {volume}
-#current_volume = 当前音量为{volume}
-#current_volume = よく聞いてね!今の音量は{volume}!
ちゃんと覚える:大音量で耳が悪くなる!
-#
-#bad_command = {command}: command not found
-#bad_command = {command}: 未知命令,键入'!help'以获取可用命令列表
-#bad_command = {command}がなに?食べれる?おいしいでしか?
diff --git a/constants.py b/constants.py
index 72cfec0..2db849d 100644
--- a/constants.py
+++ b/constants.py
@@ -1,24 +1,45 @@
+import json
+
import variables as var
+lang_dict = {}
-def tr(option, *argv, **kwargs):
+
+def load_lang(lang):
+ global lang_dict
+ with open(f"lang/{lang}", "r") as f:
+ lang_dict = json.load(f)
+
+
+def tr_cli(option, *argv, **kwargs):
try:
- string = var.config.get("strings", option)
+ string = lang_dict['cli'][option]
except KeyError:
- raise KeyError("Missed strings in configuration file: '{string}'. ".format(string=option)
- + "Please restore you configuration file back to default if necessary.")
+ raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
+ return _tr(string, *argv, **kwargs)
+
+
+def tr_web(option, *argv, **kwargs):
+ try:
+ string = lang_dict['web'][option]
+ except KeyError:
+ raise KeyError("Missed strings in language file: '{string}'. ".format(string=option))
+ return _tr(string, *argv, **kwargs)
+
+
+def _tr(string, *argv, **kwargs):
if argv or kwargs:
try:
formatted = string.format(*argv, **kwargs)
return formatted
except KeyError as e:
raise KeyError(
- "Missed/Unexpected placeholder {{{placeholder}}} in string '{string}'. ".format(placeholder=str(e).strip("'"), string=option)
- + "Please restore you configuration file back to default if necessary.")
+ "Missed/Unexpected placeholder {{{placeholder}}} in string "
+ "'{string}'. ".format(placeholder=str(e).strip("'"),
+ string=string))
except TypeError:
raise KeyError(
- "Missed placeholder in string '{string}'. ".format(string=option)
- + "Please restore you configuration file back to default if necessary.")
+ "Missed placeholder in string '{string}'. ".format(string=string))
else:
return string
@@ -28,5 +49,4 @@ def commands(command):
string = var.config.get("commands", command)
return string
except KeyError:
- raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command)
- + "Please restore you configuration file back to default if necessary.")
+ raise KeyError("Missed command in configuration file: '{string}'. ".format(string=command))
diff --git a/lang/translate.py b/lang/translate.py
new file mode 100644
index 0000000..bb9dd2f
--- /dev/null
+++ b/lang/translate.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+import requests
+
+base_url = "https://translate.azlux.fr/api/v1"
+client = "be8215d4-2417-49db-9355-c418f26dc3f4"
+secret = "MIMvdnECLkmTZyCQT4DekONN53EOSsj3"
+project_id = "4aafb197-3282-47b3-a197-0ca870cf6ab2"
+
+data = {"grant_type": "client_credentials",
+ "client_id": client,
+ "client_secret": secret}
+
+r = requests.post(f"{base_url}/auth/token", json=data)
+token = r.json()["access_token"]
+
+headers = {"Authorization": "Bearer " + token,
+ "Accept": "application/json, text/plain, */*"}
+
+r = requests.get(f"{base_url}/projects/{project_id}/translations", headers=headers)
+translations = r.json()['data']
+for translation in translations:
+ lang_code = translation['locale']['code']
+ params = {'locale': lang_code,
+ 'format': 'jsonnested'}
+ r = requests.get(f"{base_url}/projects/{project_id}/exports", params=params, headers=headers)
+ with open(lang_code, "wb")as f:
+ f.write(r.content)
diff --git a/media/file.py b/media/file.py
index 2686bfd..57ccba6 100644
--- a/media/file.py
+++ b/media/file.py
@@ -9,7 +9,7 @@ from PIL import Image
import util
import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError
-import constants
+from constants import tr_cli as tr
'''
type : file
@@ -75,7 +75,7 @@ class FileItem(BaseItem):
if not os.path.exists(self.uri()):
self.log.info(
"file: music file missed for %s" % self.format_debug_string())
- raise ValidationFailedError(constants.tr('file_missed', file=self.path))
+ raise ValidationFailedError(tr('file_missed', file=self.path))
if self.duration == 0:
self.duration = util.get_media_duration(self.uri())
@@ -185,14 +185,14 @@ class FileItem(BaseItem):
)
def format_song_string(self, user):
- return constants.tr("file_item",
- title=self.title,
- artist=self.artist if self.artist else '??',
- user=user
- )
+ return tr("file_item",
+ title=self.title,
+ artist=self.artist if self.artist else '??',
+ user=user
+ )
def format_current_playing(self, user):
- display = constants.tr("now_playing", item=self.format_song_string(user))
+ display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail:
thumbnail_html = '
'
@@ -208,4 +208,4 @@ class FileItem(BaseItem):
return title
def display_type(self):
- return constants.tr("file")
+ return tr("file")
diff --git a/media/radio.py b/media/radio.py
index 1accd31..ae82a73 100644
--- a/media/radio.py
+++ b/media/radio.py
@@ -7,7 +7,7 @@ import hashlib
from media.item import BaseItem
from media.item import item_builders, item_loaders, item_id_generators
-import constants
+from constants import tr_cli as tr
log = logging.getLogger("bot")
@@ -147,18 +147,18 @@ class RadioItem(BaseItem):
)
def format_song_string(self, user):
- return constants.tr("radio_item",
- url=self.url,
- title=get_radio_title(self.url), # the title of current song
- name=self.title, # the title of radio station
- user=user
- )
+ return tr("radio_item",
+ url=self.url,
+ title=get_radio_title(self.url), # the title of current song
+ name=self.title, # the title of radio station
+ user=user
+ )
def format_current_playing(self, user):
- return constants.tr("now_playing", item=self.format_song_string(user))
+ return tr("now_playing", item=self.format_song_string(user))
def format_title(self):
return self.title if self.title else self.url
def display_type(self):
- return constants.tr("radio")
+ return tr("radio")
diff --git a/media/url.py b/media/url.py
index 529d35d..d4d1aa8 100644
--- a/media/url.py
+++ b/media/url.py
@@ -9,7 +9,7 @@ import glob
from io import BytesIO
import base64
-import constants
+from constants import tr_cli as tr
import media
import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError, \
@@ -99,7 +99,7 @@ class URLItem(BaseItem):
# Check the length, useful in case of playlist, it wasn't checked before)
log.info(
"url: " + self.url + " has a duration of " + str(self.duration / 60) + " min -- too long")
- raise ValidationFailedError(constants.tr('too_long', song=self.title))
+ raise ValidationFailedError(tr('too_long', song=self.title))
else:
self.ready = "validated"
self.version += 1 # notify wrapper to save me
@@ -138,7 +138,7 @@ class URLItem(BaseItem):
if not succeed:
self.ready = 'failed'
self.log.error("url: error while fetching info from the URL")
- raise ValidationFailedError(constants.tr('unable_download', item=self.format_title()))
+ raise ValidationFailedError(tr('unable_download', item=self.format_title()))
def _download(self):
media.system.clear_tmp_folder(var.tmp_folder, var.config.getint('bot', 'tmp_folder_max_size'))
@@ -193,7 +193,7 @@ class URLItem(BaseItem):
os.remove(f)
self.ready = "failed"
self.downloading = False
- raise PreparationFailedError(constants.tr('unable_download', item=self.format_title()))
+ raise PreparationFailedError(tr('unable_download', item=self.format_title()))
def _read_thumbnail_from_file(self, path_thumbnail):
if os.path.isfile(path_thumbnail):
@@ -226,14 +226,14 @@ class URLItem(BaseItem):
def format_song_string(self, user):
if self.ready in ['validated', 'yes']:
- return constants.tr("url_item",
+ return tr("url_item",
title=self.title if self.title else "??",
url=self.url,
user=user)
return self.url
def format_current_playing(self, user):
- display = constants.tr("now_playing", item=self.format_song_string(user))
+ display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail:
thumbnail_html = ':
return self.title if self.title.strip() else self.url
def display_type(self):
- return constants.tr()