From 3e5b060a372fa8656c26f7aa50de06e099005ef2 Mon Sep 17 00:00:00 2001
From: Bob Swift
Date: Wed, 8 Sep 2021 17:32:14 -0600
Subject: [PATCH] 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.
---
picard/config_upgrade.py | 5 ++
picard/mbjson.py | 76 +++++++++++++------------
picard/profile.py | 2 +-
picard/ui/options/metadata.py | 95 ++++++++++++++++++++++++++++----
picard/ui/ui_options_metadata.py | 20 +++++--
test/test_acoustid.py | 3 +-
test/test_mbjson.py | 3 +-
ui/options_metadata.ui | 23 +++++++-
8 files changed, 167 insertions(+), 60 deletions(-)
diff --git a/picard/config_upgrade.py b/picard/config_upgrade.py
index 354d3d5f1..04d565876 100644
--- a/picard/config_upgrade.py
+++ b/picard/config_upgrade.py
@@ -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):
diff --git a/picard/mbjson.py b/picard/mbjson.py
index e5e93a1a4..7509d12f4 100644
--- a/picard/mbjson.py
+++ b/picard/mbjson.py
@@ -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)
diff --git a/picard/profile.py b/picard/profile.py
index 6f85accf5..22d0c9112 100644
--- a/picard/profile.py
+++ b/picard/profile.py
@@ -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"]),
diff --git a/picard/ui/options/metadata.py b/picard/ui/options/metadata.py
index a0e98ec85..111d24185 100644
--- a/picard/ui/options/metadata.py
+++ b/picard/ui/options/metadata.py
@@ -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)
diff --git a/picard/ui/ui_options_metadata.py b/picard/ui/ui_options_metadata.py
index fe6c2b60e..24191fba5 100644
--- a/picard/ui/ui_options_metadata.py
+++ b/picard/ui/ui_options_metadata.py
@@ -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(_("
Only scripts where the percentage of the artist name that is written using that script are above the specified minimum weighting will be considered.
"))
self.label.setText(_("Minimum Weighting:"))
diff --git a/test/test_acoustid.py b/test/test_acoustid.py
index 57da56dae..13caca47c 100644
--- a/test/test_acoustid.py
+++ b/test/test_acoustid.py
@@ -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,
}
diff --git a/test/test_mbjson.py b/test/test_mbjson.py
index 63189b7a6..003c4da2b 100644
--- a/test/test_mbjson.py
+++ b/test/test_mbjson.py
@@ -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'],
}
diff --git a/ui/options_metadata.ui b/ui/options_metadata.ui
index 558fba09f..c7431ff21 100644
--- a/ui/options_metadata.ui
+++ b/ui/options_metadata.ui
@@ -35,12 +35,30 @@
-
- Translate artist names to this locale where possible:
+ Translate artist names to these locales where possible:
-
-
+
+
+ 0
+
+
-
+
+
+ true
+
+
+
+ -
+
+
+ Select
+
+
+
+
-
@@ -289,7 +307,6 @@
translate_artist_names
- artist_locale
translate_artist_names_script_exception
standardize_artists
standardize_instruments