Add ability to use multiple translation locales:

- Add new `artist_locales` setting and remove old `artist_locale`.
- Add new translation locales selector and ordering screen.
- Cycle through selected translation locales until a match is found.
- Update tests.
This commit is contained in:
Bob Swift
2021-09-08 17:32:14 -06:00
parent e8aa0b3cd6
commit 3e5b060a37
8 changed files with 167 additions and 60 deletions

View File

@@ -10,6 +10,7 @@
# Copyright (C) 2016 Suhas
# Copyright (C) 2016-2017 Sambhav Kothari
# Copyright (C) 2021 Gabriel Ferreira
# Copyright (C) 2021 Bob Swift
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -428,6 +429,10 @@ def upgrade_to_v2_7_0_dev_4(config):
if _s["artist_script_exception"]:
_s["artist_script_exceptions"] = [_s["artist_script_exception"]]
_s.remove("artist_script_exception")
ListOption("setting", "artist_locales", ['en'])
if _s["artist_locale"]:
_s["artist_locales"] = [_s["artist_locale"]]
_s.remove("artist_locale")
def rename_option(config, old_opt, new_opt, option_type, default):

View File

@@ -10,6 +10,7 @@
# Copyright (C) 2020 David Kellner
# Copyright (C) 2020 dukeyin
# Copyright (C) 2021 Vladislav Karbovskii
# Copyright (C) 2021 Bob Swift
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -192,9 +193,6 @@ def _translate_artist_node(node):
config = get_config()
transl, translsort = None, None
if config.setting['translate_artist_names']:
locale = config.setting["artist_locale"]
lang = locale.split("_")[0]
if config.setting['translate_artist_names_script_exception']:
threshhold = config.setting["artist_script_exception_weighting"] / 100
detected_scripts = list_script_weighted(node["name"], threshhold)
@@ -202,39 +200,45 @@ def _translate_artist_node(node):
if script_id in detected_scripts:
return node['name'], node['sort-name']
if "aliases" in node:
result = (-1, (None, None))
for alias in node['aliases']:
if not alias["primary"]:
continue
if "locale" not in alias:
continue
parts = []
if alias['locale'] == locale:
score = 0.8
elif alias['locale'] == lang:
score = 0.6
elif alias['locale'].split("_")[0] == lang:
score = 0.4
else:
continue
parts.append((score, 5))
if alias["type"] == "Artist name":
score = 0.8
elif alias["type"] == "Legal Name":
score = 0.5
else:
# as 2014/09/19, only Artist or Legal names should have the
# Primary flag
score = 0.0
parts.append((score, 5))
comb = linear_combination_of_weights(parts)
if comb > result[0]:
result = (comb, (alias['name'], alias["sort-name"]))
transl, translsort = result[1]
if not transl:
translsort = node['sort-name']
transl = translate_from_sortname(node['name'] or "", translsort)
for locale in config.setting["artist_locales"]:
lang = locale.split("_")[0]
if "aliases" in node:
result = (-1, (None, None))
for alias in node['aliases']:
if not alias["primary"]:
continue
if "locale" not in alias:
continue
parts = []
if alias['locale'] == locale:
score = 0.8
elif alias['locale'] == lang:
score = 0.6
elif alias['locale'].split("_")[0] == lang:
score = 0.4
else:
continue
parts.append((score, 5))
if alias["type"] == "Artist name":
score = 0.8
elif alias["type"] == "Legal Name":
score = 0.5
else:
# as 2014/09/19, only Artist or Legal names should have the
# Primary flag
score = 0.0
parts.append((score, 5))
comb = linear_combination_of_weights(parts)
if comb > result[0]:
result = (comb, (alias['name'], alias["sort-name"]))
transl, translsort = result[1]
if transl:
# Matching locale found. Skip further processing.
return (transl, translsort)
if not transl:
translsort = node['sort-name']
transl = translate_from_sortname(node['name'] or "", translsort)
else:
transl, translsort = node['name'], node['sort-name']
return (transl, translsort)

View File

@@ -43,7 +43,7 @@ class UserProfileGroups():
SettingDesc("va_name", N_("Various Artists name"), ["va_name"]),
SettingDesc("nat_name", N_("Non-album tracks name"), ["nat_name"]),
SettingDesc("translate_artist_names", N_("Translate artist names"), ["translate_artist_names"]),
SettingDesc("artist_locale", N_("Translation locale"), ["artist_locale"]),
SettingDesc("artist_locales", N_("Translation locales"), ["selected_locales"]),
SettingDesc("translate_artist_names_script_exception", N_("Translate artist names exception"), ["translate_artist_names_script_exception"]),
SettingDesc("artist_script_exceptions", N_("Translation script exceptions"), ["ignore_tx_scripts"]),
SettingDesc("artist_script_exception_weighting", N_("Exception script weighting"), ["minimum_weighting"]),

View File

@@ -10,6 +10,7 @@
# Copyright (C) 2014 Wieland Hoffmann
# Copyright (C) 2017 Sambhav Kothari
# Copyright (C) 2021 Vladislav Karbovskii
# Copyright (C) 2021 Bob Swift
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -43,10 +44,13 @@ from picard.const import (
SCRIPTS,
)
from picard.ui import PicardDialog
from picard.ui.moveable_list_view import MoveableListView
from picard.ui.options import (
OptionsPage,
register_options_page,
)
from picard.ui.ui_multi_locale_selector import Ui_MultiLocaleSelector
from picard.ui.ui_options_metadata import Ui_MetadataOptionsPage
@@ -79,7 +83,7 @@ class MetadataOptionsPage(OptionsPage):
options = [
TextOption("setting", "va_name", "Various Artists"),
TextOption("setting", "nat_name", "[non-album tracks]"),
TextOption("setting", "artist_locale", "en"),
ListOption("setting", "artist_locales", ["en"]),
BoolOption("setting", "translate_artist_names", False),
BoolOption("setting", "translate_artist_names_script_exception", False),
ListOption("setting", "artist_script_exceptions", []),
@@ -98,21 +102,15 @@ class MetadataOptionsPage(OptionsPage):
self.ui.setupUi(self)
self.ui.va_name_default.clicked.connect(self.set_va_name_default)
self.ui.nat_name_default.clicked.connect(self.set_nat_name_default)
self.ui.select_locales.clicked.connect(self.open_locale_selector)
self.ui.translate_artist_names.stateChanged.connect(self.set_enabled_states)
self.ui.translate_artist_names_script_exception.stateChanged.connect(self.set_enabled_states)
def load(self):
config = get_config()
self.ui.translate_artist_names.setChecked(config.setting["translate_artist_names"])
combo_box = self.ui.artist_locale
current_locale = config.setting["artist_locale"]
for i, (locale, name, level) in enumerate(iter_sorted_locales(ALIAS_LOCALES)):
label = " " * level + name
combo_box.addItem(label, locale)
if locale == current_locale:
combo_box.setCurrentIndex(i)
self.current_locales = config.setting["artist_locales"]
self.make_locales_text()
self.ui.translate_artist_names_script_exception.setChecked(config.setting["translate_artist_names_script_exception"])
self.ui.ignore_tx_scripts.clear()
for script_id in SCRIPTS:
@@ -135,10 +133,14 @@ class MetadataOptionsPage(OptionsPage):
self.set_enabled_states()
def make_locales_text(self):
locales = list(ALIAS_LOCALES[key] for key in self.current_locales)
self.ui.selected_locales.setText('; '.join(locales))
def save(self):
config = get_config()
config.setting["translate_artist_names"] = self.ui.translate_artist_names.isChecked()
config.setting["artist_locale"] = self.ui.artist_locale.itemData(self.ui.artist_locale.currentIndex())
config.setting["artist_locales"] = self.current_locales
config.setting["translate_artist_names_script_exception"] = self.ui.translate_artist_names_script_exception.isChecked()
script_exceptions = []
for idx in range(self.ui.ignore_tx_scripts.count()):
@@ -171,9 +173,78 @@ class MetadataOptionsPage(OptionsPage):
def set_enabled_states(self):
translate_checked = self.ui.translate_artist_names.isChecked()
translate_exception_checked = self.ui.translate_artist_names_script_exception.isChecked()
self.ui.artist_locale.setEnabled(translate_checked)
self.ui.select_locales.setEnabled(translate_checked)
self.ui.selected_locales.setEnabled(translate_checked)
self.ui.translate_artist_names_script_exception.setEnabled(translate_checked)
self.ui.ignore_script_frame.setEnabled(translate_checked and translate_exception_checked)
def open_locale_selector(self):
dialog = MultiLocaleSelector(self)
dialog.show()
class MultiLocaleSelector(PicardDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MultiLocaleSelector()
self.ui.setupUi(self)
self.ui.button_box.accepted.connect(self.save_changes)
self.ui.button_box.rejected.connect(self.reject)
self.move_view = MoveableListView(self.ui.selected_locales, self.ui.move_up, self.ui.move_down)
self.ui.add_locale.clicked.connect(self.add_locale)
self.ui.remove_locale.clicked.connect(self.remove_locale)
self.ui.selected_locales.currentRowChanged.connect(self.set_button_state)
self.load()
def load(self):
self.ui.selected_locales.clear()
for locale in self.parent().current_locales:
label = (" " if '_' in locale else "") + ALIAS_LOCALES[locale]
item = QtWidgets.QListWidgetItem(label)
item.setData(QtCore.Qt.UserRole, locale)
self.ui.selected_locales.addItem(item)
self.ui.selected_locales.setCurrentRow(0)
self.ui.available_locales.clear()
for (locale, name, level) in iter_sorted_locales(ALIAS_LOCALES):
label = " " * level + name
item = QtWidgets.QListWidgetItem(label)
item.setData(QtCore.Qt.UserRole, locale)
self.ui.available_locales.addItem(item)
self.ui.available_locales.setCurrentRow(0)
self.set_button_state()
def add_locale(self):
item = self.ui.available_locales.currentItem()
if item is None:
return
locale = item.data(QtCore.Qt.UserRole)
for row in range(self.ui.selected_locales.count()):
selected_item = self.ui.selected_locales.item(row)
if selected_item.data(QtCore.Qt.UserRole) == locale:
return
new_item = item.clone()
new_item.setText(new_item.text().strip())
self.ui.selected_locales.addItem(new_item)
self.ui.selected_locales.setCurrentRow(self.ui.selected_locales.count() - 1)
def remove_locale(self):
row = self.ui.selected_locales.currentRow()
self.ui.selected_locales.takeItem(row)
def set_button_state(self):
enabled = self.ui.selected_locales.count() > 1
self.ui.remove_locale.setEnabled(enabled)
def save_changes(self):
locales = []
for row in range(self.ui.selected_locales.count()):
selected_item = self.ui.selected_locales.item(row)
locales.append(selected_item.data(QtCore.Qt.UserRole))
self.parent().current_locales = locales
self.parent().make_locales_text()
self.accept()
register_options_page(MetadataOptionsPage)

View File

@@ -27,9 +27,17 @@ class Ui_MetadataOptionsPage(object):
self.translate_artist_names = QtWidgets.QCheckBox(self.metadata_groupbox)
self.translate_artist_names.setObjectName("translate_artist_names")
self.verticalLayout_3.addWidget(self.translate_artist_names)
self.artist_locale = QtWidgets.QComboBox(self.metadata_groupbox)
self.artist_locale.setObjectName("artist_locale")
self.verticalLayout_3.addWidget(self.artist_locale)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setContentsMargins(-1, -1, -1, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.selected_locales = QtWidgets.QLineEdit(self.metadata_groupbox)
self.selected_locales.setReadOnly(True)
self.selected_locales.setObjectName("selected_locales")
self.horizontalLayout.addWidget(self.selected_locales)
self.select_locales = QtWidgets.QPushButton(self.metadata_groupbox)
self.select_locales.setObjectName("select_locales")
self.horizontalLayout.addWidget(self.select_locales)
self.verticalLayout_3.addLayout(self.horizontalLayout)
self.translate_artist_names_script_exception = QtWidgets.QCheckBox(self.metadata_groupbox)
self.translate_artist_names_script_exception.setObjectName("translate_artist_names_script_exception")
self.verticalLayout_3.addWidget(self.translate_artist_names_script_exception)
@@ -147,8 +155,7 @@ class Ui_MetadataOptionsPage(object):
self.retranslateUi(MetadataOptionsPage)
QtCore.QMetaObject.connectSlotsByName(MetadataOptionsPage)
MetadataOptionsPage.setTabOrder(self.translate_artist_names, self.artist_locale)
MetadataOptionsPage.setTabOrder(self.artist_locale, self.translate_artist_names_script_exception)
MetadataOptionsPage.setTabOrder(self.translate_artist_names, self.translate_artist_names_script_exception)
MetadataOptionsPage.setTabOrder(self.translate_artist_names_script_exception, self.standardize_artists)
MetadataOptionsPage.setTabOrder(self.standardize_artists, self.standardize_instruments)
MetadataOptionsPage.setTabOrder(self.standardize_instruments, self.convert_punctuation)
@@ -163,7 +170,8 @@ class Ui_MetadataOptionsPage(object):
def retranslateUi(self, MetadataOptionsPage):
_translate = QtCore.QCoreApplication.translate
self.metadata_groupbox.setTitle(_("Metadata"))
self.translate_artist_names.setText(_("Translate artist names to this locale where possible:"))
self.translate_artist_names.setText(_("Translate artist names to these locales where possible:"))
self.select_locales.setText(_("Select"))
self.translate_artist_names_script_exception.setText(_("Ignore artist name translation for these scripts:"))
self.label_2.setText(_("<html><head/><body><p>Only scripts where the percentage of the artist name that is written using that script are above the specified minimum weighting will be considered.</p></body></html>"))
self.label.setText(_("Minimum Weighting:"))

View File

@@ -6,6 +6,7 @@
# Copyright (C) 2018 Wieland Hoffmann
# Copyright (C) 2019-2020 Philipp Wolfer
# Copyright (C) 2020 Ray Bouchard
# Copyright (C) 2021 Bob Swift
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -38,7 +39,7 @@ settings = {
"standardize_artists": False,
"standardize_releases": False,
"translate_artist_names": True,
"artist_locale": 'en',
"artist_locales": ['en'],
"translate_artist_names_script_exception": False,
}

View File

@@ -6,6 +6,7 @@
# Copyright (C) 2017, 2019 Laurent Monin
# Copyright (C) 2018 Wieland Hoffmann
# Copyright (C) 2018-2021 Philipp Wolfer
# Copyright (C) 2021 Bob Swift
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -55,7 +56,7 @@ settings = {
"standardize_instruments": True,
"release_ars": True,
"preferred_release_countries": [],
"artist_locale": 'en',
"artist_locales": ['en'],
}

View File

@@ -35,12 +35,30 @@
<item>
<widget class="QCheckBox" name="translate_artist_names">
<property name="text">
<string>Translate artist names to this locale where possible:</string>
<string>Translate artist names to these locales where possible:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="artist_locale"/>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="selected_locales">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="select_locales">
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="translate_artist_names_script_exception">
@@ -289,7 +307,6 @@
</widget>
<tabstops>
<tabstop>translate_artist_names</tabstop>
<tabstop>artist_locale</tabstop>
<tabstop>translate_artist_names_script_exception</tabstop>
<tabstop>standardize_artists</tabstop>
<tabstop>standardize_instruments</tabstop>