diff --git a/backend/handler/metadata/ss_handler.py b/backend/handler/metadata/ss_handler.py
index 7f44c3a0c..25074297c 100644
--- a/backend/handler/metadata/ss_handler.py
+++ b/backend/handler/metadata/ss_handler.py
@@ -34,21 +34,29 @@
def get_preferred_regions(rom: Rom | None = None) -> list[str]:
- """Get preferred regions, prepending the rom's own region tags when available."""
+ """Get preferred regions, prepending the rom's own region tags when available.
+
+ When a rom is tagged with multiple regions (e.g. "(Japan, USA)"), the rom's
+ own tags are reordered according to the user's SCAN_REGION_PRIORITY so the
+ user's preference wins among the regions the file is actually tagged as.
+ Filename-tagged regions not present in the priority list keep their relative
+ order and follow the prioritized ones.
+ """
+ config = cm.get_config()
+ priority = config.SCAN_REGION_PRIORITY
+
rom_codes: list[str] = []
if rom is not None and isinstance(rom.regions, list):
for region_name in rom.regions:
code = region_name_to_provider_shortcode(region_name)
if code:
rom_codes.append(code)
+ rom_codes.sort(
+ key=lambda code: priority.index(code) if code in priority else len(priority)
+ )
- config = cm.get_config()
return list(
- dict.fromkeys(
- rom_codes
- + config.SCAN_REGION_PRIORITY
- + ["us", "wor", "ss", "eu", "jp", "cus"]
- )
+ dict.fromkeys(rom_codes + priority + ["us", "wor", "ss", "eu", "jp", "cus"])
) + ["unk"]
diff --git a/backend/tests/handler/metadata/test_ss_handler.py b/backend/tests/handler/metadata/test_ss_handler.py
index 3b7e996f6..2bb2a3ce1 100644
--- a/backend/tests/handler/metadata/test_ss_handler.py
+++ b/backend/tests/handler/metadata/test_ss_handler.py
@@ -68,6 +68,40 @@ def test_no_duplicates(self):
assert len(regions) == len(set(regions))
+ def test_multi_region_rom_respects_user_priority(self):
+ """For a multi-region ROM, the user's priority order wins among the
+ regions the file is tagged as."""
+ rom = MagicMock()
+ rom.regions = ["Japan", "USA"]
+ config = _make_config(region_priority=["us", "eu"])
+ with patch("handler.metadata.ss_handler.cm.get_config", return_value=config):
+ regions = get_preferred_regions(rom)
+
+ assert regions.index("us") < regions.index("jp")
+
+ def test_multi_region_rom_untagged_priority_does_not_win(self):
+ """A region in SCAN_REGION_PRIORITY that the file is NOT tagged as
+ should not outrank a region the file IS tagged as."""
+ rom = MagicMock()
+ rom.regions = ["Japan", "USA"]
+ config = _make_config(region_priority=["eu", "us"])
+ with patch("handler.metadata.ss_handler.cm.get_config", return_value=config):
+ regions = get_preferred_regions(rom)
+
+ assert regions.index("us") < regions.index("eu")
+ assert regions.index("jp") < regions.index("eu")
+
+ def test_multi_region_rom_unprioritized_tags_preserve_order(self):
+ """Filename regions not in the priority list keep their filename order
+ and follow the prioritized ones."""
+ rom = MagicMock()
+ rom.regions = ["Japan", "Brazil"]
+ config = _make_config(region_priority=["us"])
+ with patch("handler.metadata.ss_handler.cm.get_config", return_value=config):
+ regions = get_preferred_regions(rom)
+
+ assert regions.index("jp") < regions.index("br")
+
class TestExtractMediaFromSsGame:
"""Tests for extract_media_from_ss_game."""
diff --git a/frontend/src/locales/en_GB/scan.json b/frontend/src/locales/en_GB/scan.json
index 1255f4fc6..4fdac228a 100644
--- a/frontend/src/locales/en_GB/scan.json
+++ b/frontend/src/locales/en_GB/scan.json
@@ -37,7 +37,7 @@
"roms-scanned-with-details": "ROMs: {n_scanned_roms} scanned out of {n_total_roms}, with {n_new_roms} new and {n_identified_roms} identified",
"scan": "Scan",
"scan-options": "Scan options",
- "scan-types-info": "New Platforms: This will only look for platforms that are not already in RomM.
Quick Scan: Scans for games that are not in the library yet (fastest).
Unmatched Games: Attempts to match games that are not matched with the selected metadata sources.
For example, selecting IGDB and ScreenScraper will scan games that are not matched with IGDB or ScreenScraper.
Update Metadata: Updates the metadata for games that have been matched with selected metadata sources using the external ID (e.g. IGDB ID).
For example, selecting IGDB and ScreenScraper will update the metadata for games that are matched with IGDB or ScreenScraper, and will use igdb_id and/or ssfr_id to refetch the metadata from the respective providers.
Recalculate Hashes: Recalculates hashes for all files in the selected platforms.
Total Rescan: Rescans and rematches all games in the selected platforms (slowest).
This will wipe all existing metadata matches, including the external IDs, and attempt to match them again, like on a fresh scan. Saves, states and notes will be preserved.",
+ "scan-types-info": "New Platforms: This will only look for platforms that are not already in RomM.
Quick Scan: Scans for games that are not in the library yet (fastest).
Unmatched Games: Attempts to match games that are not matched with the selected metadata sources.
For example, selecting IGDB and ScreenScraper will scan games that are not matched with IGDB or ScreenScraper.
Update Metadata: Updates the metadata for games that have been matched with selected metadata sources using the external ID (e.g. IGDB ID).
For example, selecting IGDB and ScreenScraper will update the metadata for games that are matched with IGDB or ScreenScraper, and will use igdb_id and/or ssfr_id to refetch the metadata from the respective providers.
Recalculate Hashes: Recalculates hashes for all files in the selected platforms.
Complete Rescan: Rescans and rematches all games in the selected platforms (slowest).
This will wipe all existing metadata matches, including the external IDs, and attempt to match them again, like on a fresh scan. Saves, states and notes will be preserved.",
"scan-types-more-info": "More information",
"select-one-source": "Please select at least one metadata source to enrich your library with artwork and metadata",
"unmatched-games": "Unmatched games",
diff --git a/frontend/src/locales/en_US/scan.json b/frontend/src/locales/en_US/scan.json
index 1255f4fc6..4fdac228a 100644
--- a/frontend/src/locales/en_US/scan.json
+++ b/frontend/src/locales/en_US/scan.json
@@ -37,7 +37,7 @@
"roms-scanned-with-details": "ROMs: {n_scanned_roms} scanned out of {n_total_roms}, with {n_new_roms} new and {n_identified_roms} identified",
"scan": "Scan",
"scan-options": "Scan options",
- "scan-types-info": "New Platforms: This will only look for platforms that are not already in RomM.
Quick Scan: Scans for games that are not in the library yet (fastest).
Unmatched Games: Attempts to match games that are not matched with the selected metadata sources.
For example, selecting IGDB and ScreenScraper will scan games that are not matched with IGDB or ScreenScraper.
Update Metadata: Updates the metadata for games that have been matched with selected metadata sources using the external ID (e.g. IGDB ID).
For example, selecting IGDB and ScreenScraper will update the metadata for games that are matched with IGDB or ScreenScraper, and will use igdb_id and/or ssfr_id to refetch the metadata from the respective providers.
Recalculate Hashes: Recalculates hashes for all files in the selected platforms.
Total Rescan: Rescans and rematches all games in the selected platforms (slowest).
This will wipe all existing metadata matches, including the external IDs, and attempt to match them again, like on a fresh scan. Saves, states and notes will be preserved.",
+ "scan-types-info": "New Platforms: This will only look for platforms that are not already in RomM.
Quick Scan: Scans for games that are not in the library yet (fastest).
Unmatched Games: Attempts to match games that are not matched with the selected metadata sources.
For example, selecting IGDB and ScreenScraper will scan games that are not matched with IGDB or ScreenScraper.
Update Metadata: Updates the metadata for games that have been matched with selected metadata sources using the external ID (e.g. IGDB ID).
For example, selecting IGDB and ScreenScraper will update the metadata for games that are matched with IGDB or ScreenScraper, and will use igdb_id and/or ssfr_id to refetch the metadata from the respective providers.
Recalculate Hashes: Recalculates hashes for all files in the selected platforms.
Complete Rescan: Rescans and rematches all games in the selected platforms (slowest).
This will wipe all existing metadata matches, including the external IDs, and attempt to match them again, like on a fresh scan. Saves, states and notes will be preserved.",
"scan-types-more-info": "More information",
"select-one-source": "Please select at least one metadata source to enrich your library with artwork and metadata",
"unmatched-games": "Unmatched games",