Merge branch 'master' into emujs-netplay

This commit is contained in:
Georges-Antoine Assi
2025-12-04 10:46:21 -05:00
42 changed files with 1546 additions and 112 deletions

View File

@@ -10,6 +10,8 @@ import sqlalchemy as sa
from alembic import op
from sqlalchemy import text
from utils.database import is_mariadb, is_mysql
# revision identifiers, used by Alembic.
revision = "0057_multi_notes"
down_revision = "0056_gamelist_xml"
@@ -53,14 +55,18 @@ def upgrade() -> None:
# Create indexes for performance
op.create_index("idx_rom_notes_public", "rom_notes", ["is_public"])
op.create_index("idx_rom_notes_rom_user", "rom_notes", ["rom_id", "user_id"])
op.create_index("idx_rom_notes_title", "rom_notes", ["title"])
# Get connection for manual index creation
connection = op.get_bind()
# For MariaDB compatibility, we limit the content index length
connection.execute(text("CREATE INDEX idx_rom_notes_title ON rom_notes (title)"))
connection.execute(
text("CREATE INDEX idx_rom_notes_content ON rom_notes (content(100))")
)
if is_mysql(connection) or is_mariadb(connection):
connection.execute(
text("CREATE INDEX idx_rom_notes_content ON rom_notes (content(100))")
)
else:
op.create_index("idx_rom_notes_content", "rom_notes", ["content"])
# Add default values to old note columns to prevent insertion errors
# This allows new rom_user records to be created without specifying note fields
@@ -81,7 +87,7 @@ def upgrade() -> None:
# Migrate existing notes from rom_user to rom_notes table
# Both note_raw_markdown and note_is_public columns exist from previous migrations
connection = op.get_bind()
result = connection.execute(
text(
"""
@@ -118,6 +124,8 @@ def upgrade() -> None:
def downgrade() -> None:
"""Drop the rom_notes table and restore note columns to rom_user."""
connection = op.get_bind()
# Add back the old columns to rom_user
op.add_column(
"rom_user",
@@ -131,7 +139,7 @@ def downgrade() -> None:
)
# Migrate notes back to rom_user (take first note per user/rom)
connection = op.get_bind()
result = connection.execute(
text(
"""
@@ -161,9 +169,19 @@ def downgrade() -> None:
)
# Drop indexes and table
connection = op.get_bind()
connection.execute(text("DROP INDEX IF EXISTS idx_rom_notes_content ON rom_notes"))
connection.execute(text("DROP INDEX IF EXISTS idx_rom_notes_title ON rom_notes"))
if is_mysql(connection) or is_mariadb(connection):
# The content index was created with a specific length using raw SQL for MySQL/MariaDB,
# so we drop it with raw SQL as well.
connection.execute(
text("DROP INDEX IF EXISTS idx_rom_notes_content ON rom_notes")
)
else:
# For other databases, the content index was created with op.create_index.
op.drop_index("idx_rom_notes_content", table_name="rom_notes")
# These indexes were created with op.create_index for all dialects.
op.drop_index("idx_rom_notes_title", table_name="rom_notes")
op.drop_index("idx_rom_notes_rom_user", table_name="rom_notes")
op.drop_index("idx_rom_notes_public", table_name="rom_notes")
op.drop_table("rom_notes")

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
import re
from datetime import datetime, timezone
from typing import Annotated, NotRequired, TypedDict, get_type_hints
from typing import NotRequired, TypedDict, get_type_hints
from fastapi import Request
from pydantic import Field, computed_field, field_validator
from pydantic import computed_field, field_validator
from endpoints.responses.assets import SaveSchema, ScreenshotSchema, StateSchema
from handler.metadata.flashpoint_handler import FlashpointMetadata
@@ -261,8 +261,6 @@ class RomSchema(BaseModel):
md5_hash: str | None
sha1_hash: str | None
# TODO: Remove this after 4.3 release
multi: Annotated[bool, Field(deprecated="Replaced by has_multiple_files")]
has_simple_single_file: bool
has_nested_single_file: bool
has_multiple_files: bool
@@ -274,6 +272,8 @@ class RomSchema(BaseModel):
siblings: list[SiblingRomSchema]
rom_user: RomUserSchema
merged_screenshots: list[str]
merged_ra_metadata: RomRAMetadata | None
class Config:
from_attributes = True
@@ -360,8 +360,6 @@ class UserCollectionSchema(BaseModel):
class DetailedRomSchema(RomSchema):
merged_ra_metadata: RomRAMetadata | None
merged_screenshots: list[str]
user_saves: list[SaveSchema]
user_states: list[StateSchema]
user_screenshots: list[ScreenshotSchema]

View File

@@ -303,11 +303,6 @@ class Rom(BaseModel):
return []
# TODO: Remove this after 4.3 release
@cached_property
def multi(self) -> bool:
return self.has_nested_single_file or self.has_multiple_files
@cached_property
def has_simple_single_file(self) -> bool:
return len(self.files) == 1 and not self.files[0].is_nested
@@ -390,7 +385,7 @@ class Rom(BaseModel):
or []
)
@property
@cached_property
def merged_ra_metadata(self) -> dict[str, list] | None:
if self.ra_metadata and "achievements" in self.ra_metadata:
for achievement in self.ra_metadata.get("achievements", []):

View File

@@ -263,4 +263,970 @@ interactions:
status:
code: 200
message: OK
- request:
body: '{"mD5":"7de64234ee20788b9d74d2fdb3462aed","shA1":"77693a00418a9d8971b7a005f2001d997e359bff","crc":"d56d1c89"}'
headers:
accept:
- "*/*"
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- "109"
content-type:
- application/json-patch+json
host:
- hasheous.org
user-agent:
- RomM/development
x-client-api-key:
- JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS
method: POST
uri: https://hasheous.org/api/v1/Lookup/ByHash?returnAllSources=true&returnFields=Signatures%2C+Metadata%2C+Attributes
response:
body:
string: !!binary |
H4sIAAAAAAAA/+1Y3W7iOBi971NEuWolKLHzn7sW2gpp6I4GpnuxmgsTG7CaxFHiwLKjefe1XRpI
4jLbbkdadZGqknw/9ufj4xPb388Mw6TYjAwn8GFPvmUoJeLd/IxyUhgTVFBmKkdJlxniVUFK4f4u
LMJ2z8YZL5i0/KEsxs6jvMunpvYWYYtZJTLoUyM/eoeuBGXLCi11rnJbcpKOZaGu7zdceTVPaLki
hfJaDV9KOMKIoymriliW4jRbjVkhrRA2zAoP0wlcKwBmwxMjTpas2Er/nRhc2XS/BF0dgEkZFzTn
lGWtOOP8pipYTi7EQ9a7LXoj0rspL5rpW4IKmWfqAei6MEmZtN4zPpKPGkyVm2acZJgZfaN+9Bzj
/HrLyXSD8pzgC13ug6gcZbzb75piwrrmp7nfvjzzXU/M8m1BlytNJwVLFe++NayLBC01BBLBQ9m7
pEhtPwiRAW2q1vRwNPQArg8D2/4pAYzzr9Ori8u157QQpH8pQoLQsS2nSdu4iGUr2PUwiIOwmZhi
Vzp9TDwH2g4h0PKDYB5i38Fwgee240FEcKu3FQIqy/dCG1mWAwIU4iD0wdwXr+4CWhbAYegT2w3n
i0UnG7pedwZKLvRAgm2uSUEXtN3rG+YbkzVJWJ6SjE/r1putIi70Y15xop/m2TZXc/A1e8zYJjN1
/gnBFHVbTqV5JDSDJt2mlfMTmpNEA8SzNNZCs1fGPdvOWqz71UoJLBu8u1QCzVoInECQ7mWp/P+o
pHH+GRWCu/1hwjJyEs0WUWzPhkFgnUTzJJqvFU31++1sx79aruqN5zMJd0zLJZn6aUNcTJqmFUfz
hIyf2Gg7eyoezMtEreW9J0U8Xk0IXzGVdlVxJkw0PsitRzC+G13v7QnNHqV1xXleRoPBZrO5pEs8
v4xZOpCaXw60hSao5FMhY/FKZkMLOn3L7UN3Bu0IwMg+KDsjf3ZCPX3ohmYZzZYPjJN6WddOzjhK
9K6DvM9EjPNAEHaz3QL/RcR1aAvV/Qng92wiDTq4Zyui9uGjeRf043B6fQvMLCtSf0fhBEAf+gHh
/ELE6ruKV1QIilST8pWoSqjcGbAjx48ceARVV0CqD/2AqN7J7/01S1/HUQGR3YdgBtwIBBH0jqPp
6kM/IJpTTlB6QvI9ePnb3QnHd8DxJqex/ApNudz6nhD994j+Th9prrabJzTfSTHvCopHr/4COX1o
zyw/glbkBsfx9PShvwJP8V+dX5unmtY5oL33XguYLzPCBynKKpQMbBAcbDue7sBh4LpBbatbfz4d
TVesECesQtRldqPud+fYh/FkMlF9jLEm7AtJkLzaeG5UKsc+bI2SSplVeVr6vGVgLvT2l9z/1YF1
yhqn4rh7tbPK26CXa/vElux4ZLu8e3FBoylPHeCO4j5AOR2swYDK6soBsBzbu4G3Xjh0hq4N3Vs3
sNxrexjcBtbw+grYo8AJ4bA1JUDcJ4S940PXwnw44rcP8x9XvV9xZz/+BpBiGQ4uGgAA
headers:
CF-RAY:
- 9a4c45a82a9d4f60-SEA
Cache-Control:
- public,max-age=300
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8; x-api-version=1
Date:
- Wed, 26 Nov 2025 20:52:53 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=AmEyXjNXlqiEp08%2FQDNEeHrla2p9mayZ95eJmTfZxOuA2%2FpDsv39zgpnSpcl2zM8ZgDwWO%2BpoByCPFNkqtYquSViDuCYOqdy6zM%3D"}]}'
Server:
- cloudflare
Strict-Transport-Security:
- max-age=15552000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
api-supported-versions:
- "1.0"
cf-cache-status:
- DYNAMIC
via:
- 1.1 Caddy
x-correlation-id:
- defecc62-caa7-4fc5-b888-936938e4aee1
status:
code: 200
message: OK
- request:
body: ""
headers:
accept:
- "*/*"
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-type:
- application/json-patch+json
host:
- hasheous.org
user-agent:
- RomM/development
x-client-api-key:
- JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS
method: GET
uri: https://hasheous.org/api/v1/MetadataProxy/IGDB/Game?Id=3340&expandColumns=age_ratings%2C+alternative_names%2C+collections%2C+cover%2C+dlcs%2C+expanded_games%2C+franchise%2C+franchises%2C+game_modes%2C+genres%2C+involved_companies%2C+platforms%2C+ports%2C+remakes%2C+screenshots%2C+similar_games%2C+videos
response:
body:
string: !!binary |
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
NE9D8mqrsT/+7p//BfeBP9GQUgEA
headers:
CF-RAY:
- 9a4c45adedae4f60-SEA
Cache-Control:
- public,max-age=604800
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8; x-api-version=1
Date:
- Wed, 26 Nov 2025 20:52:54 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=z%2BSdx55RUYQGYaVnJJGUYgM%2FPmSfCXlPqA5qv36XdOff0WJ3%2BW%2BQIUJmgp%2Feyd4PzVuLCrqKXcHiEeQZWPisgzDjLV6p9294250%3D"}]}'
Server:
- cloudflare
Strict-Transport-Security:
- max-age=15552000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
api-supported-versions:
- "1.0"
cf-cache-status:
- DYNAMIC
via:
- 1.1 Caddy
x-correlation-id:
- 626fab1b-06f5-42a3-a0c9-018328ed6ad3
status:
code: 200
message: OK
- request:
body: '{"mD5":"7de64234ee20788b9d74d2fdb3462aed","shA1":"77693a00418a9d8971b7a005f2001d997e359bff","crc":"d56d1c89"}'
headers:
accept:
- "*/*"
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- "109"
content-type:
- application/json-patch+json
host:
- hasheous.org
user-agent:
- RomM/development
x-client-api-key:
- JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS
method: POST
uri: https://hasheous.org/api/v1/Lookup/ByHash?returnAllSources=true&returnFields=Signatures%2C+Metadata%2C+Attributes
response:
body:
string: !!binary |
H4sIAAAAAAAA/+1YXW/iOBR976+I8tRKUGLnO28ttBXS0B0NTPdhNQ8mMWA1saPEgWVH89/Xdmkg
ictsux1p1UXqR3Lv9fX18fGJ7e9nhmGSxIwMJ/BhT75RlGHxbn5GOS6MCSoIM5WjJEuKeFXgUri/
C4uw3bMx5QWTlj+Uxdh5lHf5lGpvEbaYVaIFeUryo3foShFdVmipc5XbkuNsLAt1fb/hyqt5SsoV
LpTXavgyzFGCOJqyqohlKU4za8wKaYWwYVZ4mE7gWgEwG54YcbxkxVb678Tgyqb7JejqgASXcUFy
ThhtxRnnN1XBcnwhHmjvtuiNcO+mvGg232JUyHamHoCuK8EZk9Z7xkfyUYOpchPKMU2Y0TfqR88x
zq+3HE83KM9xcqFr+yAqR5R3+12TBLOu+Wnuty/PfNcTs3xbkOVK00nBMsW7bw3rIkVLDYFE8FD2
LilS2w9CZECbqjU9HA09gOvDwLZ/SgDj/Ov06uJy7TktBMlfipAgdGzLadI2LmKZJXG9BMRB2GyY
Ja50+gn2HGg7GEPLD4J5mPhOAhfJ3HY8iHDS6m2FgGrle6GNLMsBAQqTIPTB3Bev7gJaFkjC0Me2
G84Xi05r6HrdGSi50AMJtrnGBVmQdq9vmO8Er3HK8gxTPq2zN7MiLvRjXnGsn+bZNldz8JU+Urah
ps4/wQlB3cyZNI+EZpC0m1o5P6E5TjVAPEtjLTR7Zdyz7azFul+tlMCywbtLJdCshcAJBOlelsr/
j0oa559RIbjbH6aM4pNotohiezYMAuskmifRfK1oqv/fznb8q+Wq3ng+k3DHtFySqZ81xMUkWVZx
NE/x+ImNtrOn4sG8TNRa3nsyxOPVBPMVU82uKs6EicQHbesRjO9G13t7SuijtK44z8toMNhsNpdk
mcwvY5YNxG+OqND3gbbYFJV8KqQsXskM0IJO33L70J1BOwIwsg9Kp/jPTqinD90QSgldPjCO66Vd
OznjKNW7Dtp9xmKsB6Kwm/HWBLyIug5xobw/Af2eTaRBB/lshdVefDTvAn8cTq9vgZllRernKJwA
6EM/IJxfsFiBV/GKCFGRilK+ElUJlTsDduT4kQOPoOoKSPWhHxDVO/nNv2bZ6zgqILL7EMyAG4Eg
gt5xNF196AdEc8oxyk5Ivgcvf7s74fgOON7kJJZfoSmX298Tov8e0d/JI8nVlvOE5jsp5l1BktGr
v0BOH9ozy4+gFbnBcTw9feivwFP8VWfY5smmdRZo77/XAuZLivkgQ7RC6cAGwcG24+keHAauG9S2
OvvzCWm6YoU4ZRWiLrMbdb87yz6MJ5OJ6mOcaMK+4BTJ643npFI59mFrlFbKrMrT0uctA3Oht7/o
/q8OrFPWOBNH3qudVd4IvVzbJ7ZkxyPb5d2LSxpNeeoQdxT3AcrJYA0GRFZXDoDl2N4NvPXCoTN0
bejeuoHlXtvD4DawhtdXwB4FTgiHrSkB4k4h7B0fuhbmwxG/fZj/uOr9ijv78TfXAoVbMhoAAA==
headers:
CF-RAY:
- 9a4c45d4ae25d469-SEA
Cache-Control:
- public,max-age=300
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8; x-api-version=1
Date:
- Wed, 26 Nov 2025 20:53:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=ybytLODqS7H3HT2uAtDLP1os12VWdlxe6t%2Fgd%2Bw1YaC070sB60CJ39TfhOupJncwPf0pMioyLgnTCs%2FcbF%2BF3AQUZOPQX7hY1Ds%3D"}]}'
Server:
- cloudflare
Strict-Transport-Security:
- max-age=15552000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
api-supported-versions:
- "1.0"
cf-cache-status:
- DYNAMIC
via:
- 1.1 Caddy
x-correlation-id:
- fc44b4f9-611e-4d44-aa5e-0b793fc4c5ab
status:
code: 200
message: OK
- request:
body: ""
headers:
accept:
- "*/*"
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-type:
- application/json-patch+json
host:
- hasheous.org
user-agent:
- RomM/development
x-client-api-key:
- JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS
method: GET
uri: https://hasheous.org/api/v1/MetadataProxy/IGDB/Game?Id=3340&expandColumns=age_ratings%2C+alternative_names%2C+collections%2C+cover%2C+dlcs%2C+expanded_games%2C+franchise%2C+franchises%2C+game_modes%2C+genres%2C+involved_companies%2C+platforms%2C+ports%2C+remakes%2C+screenshots%2C+similar_games%2C+videos
response:
body:
string: !!binary |
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
headers:
Age:
- "6"
CF-RAY:
- 9a4c45d67f8ed469-SEA
Cache-Control:
- public,max-age=604800
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 26 Nov 2025 20:53:00 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=87x8jDKQRGXZXBcJ8XojaRzOW%2B7PcM7rREtPuTI1DIDOL7wBGPTwDAcgPa4HBhAkxxQNHo6SbvMzDixBvndz%2FPGZ%2Fz8qgImPJfs%3D"}]}'
Server:
- cloudflare
Strict-Transport-Security:
- max-age=15552000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
via:
- 1.1 Caddy
x-correlation-id:
- 626fab1b-06f5-42a3-a0c9-018328ed6ad3
status:
code: 200
message: OK
version: 1

View File

@@ -17,6 +17,7 @@ async def test_scan_platform():
assert platform.slug == "n64"
assert platform.name == "Nintendo 64"
assert platform.igdb_id == 4
assert platform.hasheous_id == 64
async with initialize_context():
platform = await scan_platform("", [])
@@ -25,14 +26,18 @@ async def test_scan_platform():
assert platform.slug == ""
assert platform.name == ""
assert platform.igdb_id is None
assert platform.hasheous_id is None
@pytest.mark.vcr
async def test_scan_rom():
platform = Platform(id=1, slug="n64", fs_slug="n64", name="Nintendo 64", igdb_id=4)
platform = Platform(
id=1, slug="n64", fs_slug="n64", name="Nintendo 64", igdb_id=4, hasheous_id=64
)
platform = db_platform_handler.add_platform(platform)
rom = Rom(
platform_id=platform.id,
fs_name="Paper Mario (USA).z64",
fs_name_no_tags="Paper Mario",
fs_name_no_ext="Paper Mario",
@@ -40,6 +45,7 @@ async def test_scan_rom():
fs_path="n64/Paper Mario (USA)",
name="Paper Mario",
igdb_id=3340,
hasheous_id=4872,
fs_size_bytes=1024,
tags=[],
)
@@ -55,18 +61,22 @@ async def test_scan_rom():
"nested": False,
"files": [
RomFile(
rom=rom,
file_name="Paper Mario (USA).z64",
file_path="n64/Paper Mario (USA)",
file_size_bytes=1024,
file_size_bytes=23175094,
last_modified=1620000000,
crc_hash="d56d1c89",
md5_hash="7de64234ee20788b9d74d2fdb3462aed",
sha1_hash="77693a00418a9d8971b7a005f2001d997e359bff",
)
],
"crc_hash": "",
"md5_hash": "",
"sha1_hash": "",
"crc_hash": "d56d1c89",
"md5_hash": "7de64234ee20788b9d74d2fdb3462aed",
"sha1_hash": "77693a00418a9d8971b7a005f2001d997e359bff",
"ra_hash": "",
},
metadata_sources=[MetadataSource.IGDB],
metadata_sources=[MetadataSource.HASHEOUS],
newly_added=True,
)
@@ -75,5 +85,6 @@ async def test_scan_rom():
assert rom.name == "Paper Mario"
assert rom.fs_path == "n64/Paper Mario (USA)"
assert rom.igdb_id == 3340
assert rom.fs_size_bytes == 1024
assert rom.hasheous_id == 4872
assert rom.fs_size_bytes == 23175094
assert rom.tags == []

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -74,10 +74,6 @@ export type DetailedRomSchema = {
crc_hash: (string | null);
md5_hash: (string | null);
sha1_hash: (string | null);
/**
* @deprecated
*/
multi: boolean;
has_simple_single_file: boolean;
has_nested_single_file: boolean;
has_multiple_files: boolean;

View File

@@ -11,6 +11,7 @@ import type { RomIGDBMetadata } from './RomIGDBMetadata';
import type { RomLaunchboxMetadata } from './RomLaunchboxMetadata';
import type { RomMetadataSchema } from './RomMetadataSchema';
import type { RomMobyMetadata } from './RomMobyMetadata';
import type { RomRAMetadata } from './RomRAMetadata';
import type { RomSSMetadata } from './RomSSMetadata';
import type { RomUserSchema } from './RomUserSchema';
import type { SiblingRomSchema } from './SiblingRomSchema';
@@ -68,10 +69,6 @@ export type SimpleRomSchema = {
crc_hash: (string | null);
md5_hash: (string | null);
sha1_hash: (string | null);
/**
* @deprecated
*/
multi: boolean;
has_simple_single_file: boolean;
has_nested_single_file: boolean;
has_multiple_files: boolean;
@@ -82,5 +79,7 @@ export type SimpleRomSchema = {
missing_from_fs: boolean;
siblings: Array<SiblingRomSchema>;
rom_user: RomUserSchema;
merged_ra_metadata: (RomRAMetadata | null);
merged_screenshots: Array<string>;
};

View File

@@ -10,8 +10,9 @@ import RSection from "@/components/common/RSection.vue";
import romApi from "@/services/api/rom";
import storeAuth from "@/stores/auth";
import type { DetailedRom } from "@/stores/roms";
import { toBrowserLocale } from "@/utils";
const { t } = useI18n();
const { t, locale } = useI18n();
const theme = useTheme();
const auth = storeAuth();
const { scopes } = storeToRefs(auth);
@@ -381,7 +382,11 @@ watch(
class="text-caption mt-2 mb-2"
>
{{ t("common.last-updated") }}:
{{ new Date(note.updated_at).toLocaleString() }}
{{
new Date(note.updated_at).toLocaleString(
toBrowserLocale(locale),
)
}}
</v-card-subtitle>
</v-expansion-panel-text>
</v-expansion-panel>
@@ -442,7 +447,11 @@ watch(
class="text-caption mt-2 mb-2"
>
{{ t("common.last-updated") }}:
{{ new Date(note.updated_at).toLocaleString() }}
{{
new Date(note.updated_at).toLocaleString(
toBrowserLocale(locale),
)
}}
</v-card-subtitle>
</v-expansion-panel-text>
</v-expansion-panel>

View File

@@ -14,7 +14,11 @@ import RSection from "@/components/common/RSection.vue";
import romApi from "@/services/api/rom";
import storeAuth from "@/stores/auth";
import type { DetailedRom } from "@/stores/roms";
import { getTextForStatus, getEmojiForStatus } from "@/utils";
import {
getTextForStatus,
getEmojiForStatus,
getI18nKeyForStatus,
} from "@/utils";
const { t } = useI18n();
const props = defineProps<{ rom: DetailedRom }>();
@@ -301,7 +305,7 @@ watch(
getEmojiForStatus(item.raw as RomUserStatus)
}}</span
><span class="ml-2">{{
getTextForStatus(item.raw as RomUserStatus)
t(getI18nKeyForStatus(item.raw as RomUserStatus) || "")
}}</span>
</template>
<template #item="{ item }">
@@ -310,7 +314,7 @@ watch(
getEmojiForStatus(item.raw as RomUserStatus)
}}</span
><span class="ml-2">{{
getTextForStatus(item.raw as RomUserStatus)
t(getI18nKeyForStatus(item.raw as RomUserStatus) || "")
}}</span>
</v-list-item>
</template>

View File

@@ -2,6 +2,7 @@
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, ref } from "vue";
import { useI18n } from "vue-i18n";
import type { SaveSchema } from "@/__generated__";
import EmptySaves from "@/components/common/EmptyStates/EmptySaves.vue";
import storeAuth from "@/stores/auth";
@@ -10,6 +11,7 @@ import type { Events } from "@/types/emitter";
import { formatBytes, formatTimestamp } from "@/utils";
import { getEmptyCoverImage } from "@/utils/covers";
const { t, locale } = useI18n();
const auth = storeAuth();
const { scopes } = storeToRefs(auth);
const props = defineProps<{ rom: DetailedRom }>();
@@ -181,7 +183,8 @@ function onCardClick(save: SaveSchema, event: MouseEvent) {
</v-col>
<v-col cols="12">
<v-chip size="x-small" label>
Updated: {{ formatTimestamp(save.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(save.updated_at, locale) }}
</v-chip>
</v-col>
</v-row>

View File

@@ -2,6 +2,7 @@
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, ref } from "vue";
import { useI18n } from "vue-i18n";
import type { StateSchema } from "@/__generated__";
import EmptySates from "@/components/common/EmptyStates/EmptyStates.vue";
import storeAuth from "@/stores/auth";
@@ -10,6 +11,7 @@ import type { Events } from "@/types/emitter";
import { formatBytes, formatTimestamp } from "@/utils";
import { getEmptyCoverImage } from "@/utils/covers";
const { t, locale } = useI18n();
const auth = storeAuth();
const { scopes } = storeToRefs(auth);
const props = defineProps<{ rom: DetailedRom }>();
@@ -183,7 +185,8 @@ function onCardClick(state: StateSchema, event: MouseEvent) {
</v-col>
<v-col cols="12">
<v-chip size="x-small" label>
Updated: {{ formatTimestamp(state.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(state.updated_at, locale) }}
</v-chip>
</v-col>
</v-row>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDisplay } from "vuetify";
import FavBtn from "@/components/common/Game/FavBtn.vue";
import MissingFromFSIcon from "@/components/common/MissingFromFSIcon.vue";
@@ -8,12 +9,14 @@ import PlatformIcon from "@/components/common/Platform/PlatformIcon.vue";
import { ROUTES } from "@/plugins/router";
import storePlatforms from "@/stores/platforms";
import type { DetailedRom } from "@/stores/roms";
import { toBrowserLocale } from "@/utils";
const props = defineProps<{ rom: DetailedRom }>();
const { smAndDown } = useDisplay();
const { locale } = useI18n();
const releaseDate = new Date(
Number(props.rom.metadatum.first_release_date),
).toLocaleDateString("en-US", {
).toLocaleDateString(toBrowserLocale(locale.value), {
day: "2-digit",
month: "short",
year: "numeric",

View File

@@ -5,10 +5,13 @@ import {
formatDuration,
} from "date-fns";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { formatTimestamp } from "@/utils";
import { TaskStatusItem, type TaskStatusResponse } from "@/utils/tasks";
import TaskProgressDisplay from "./tasks/TaskProgressDisplay.vue";
const { locale } = useI18n();
const props = defineProps<{
task: TaskStatusResponse;
}>();
@@ -89,6 +92,7 @@ const taskDistanceFromNow = computed(() => {
:title="
formatTimestamp(
task.started_at || task.enqueued_at || task.created_at,
locale,
)
"
>

View File

@@ -13,7 +13,7 @@ import storeUsers, { type User } from "@/stores/users";
import type { Events } from "@/types/emitter";
import { defaultAvatarPath, formatTimestamp, getRoleIcon } from "@/utils";
const { t } = useI18n();
const { t, locale } = useI18n();
const userSearch = ref("");
const emitter = inject<Emitter<Events>>("emitter");
const usersStore = storeUsers();
@@ -156,7 +156,7 @@ onMounted(() => {
</v-list-item>
</template>
<template #item.last_active="{ item }">
{{ formatTimestamp(item.last_active) }}
{{ formatTimestamp(item.last_active, locale) }}
</template>
<template #item.enabled="{ item }">
<v-switch

View File

@@ -2,14 +2,16 @@
import { useLocalStorage } from "@vueuse/core";
import { identity } from "lodash";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { type SimpleRom } from "@/stores/roms";
import {
languageToEmoji,
regionToEmoji,
getEmojiForStatus,
getTextForStatus,
getI18nKeyForStatus,
} from "@/utils";
const { t } = useI18n();
const props = defineProps<{ rom: SimpleRom }>();
const showRegions = useLocalStorage("settings.showRegions", true);
const showLanguages = useLocalStorage("settings.showLanguages", true);
@@ -54,7 +56,7 @@ const playingStatus = computed(() => {
v-if="playingStatus && showStatus"
class="translucent mr-1 mt-1 px-2"
density="compact"
:title="getTextForStatus(playingStatus)"
:title="t(getI18nKeyForStatus(playingStatus) || '')"
>
{{ getEmojiForStatus(playingStatus) }}
</v-chip>

View File

@@ -10,7 +10,7 @@ import type { Events } from "@/types/emitter";
import { formatBytes, formatTimestamp } from "@/utils";
import { getEmptyCoverImage } from "@/utils/covers";
const { t } = useI18n();
const { t, locale } = useI18n();
const { mdAndUp } = useDisplay();
const show = ref(false);
const rom = ref<DetailedRom | null>(null);
@@ -85,7 +85,8 @@ function closeDialog() {
{{ formatBytes(save.file_size_bytes) }}
</v-chip>
<v-chip size="x-small" label>
Updated: {{ formatTimestamp(save.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(save.updated_at, locale) }}
</v-chip>
</v-row>
</v-card-text>

View File

@@ -10,7 +10,7 @@ import type { Events } from "@/types/emitter";
import { formatBytes, formatTimestamp } from "@/utils";
import { getEmptyCoverImage } from "@/utils/covers";
const { t } = useI18n();
const { t, locale } = useI18n();
const { mdAndUp } = useDisplay();
const show = ref(false);
const rom = ref<DetailedRom | null>(null);
@@ -85,7 +85,8 @@ function closeDialog() {
{{ formatBytes(state.file_size_bytes) }}
</v-chip>
<v-chip size="x-small" label>
Updated: {{ formatTimestamp(state.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(state.updated_at, locale) }}
</v-chip>
</v-row>
</v-card-text>

View File

@@ -9,10 +9,11 @@ import RDialog from "@/components/common/RDialog.vue";
import romApi from "@/services/api/rom";
import type { SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { toBrowserLocale } from "@/utils";
const theme = useTheme();
const emitter = inject<Emitter<Events>>("emitter");
const { t } = useI18n();
const { t, locale } = useI18n();
const rom = ref<SimpleRom | null>(null);
const show = ref(false);
@@ -115,7 +116,11 @@ function closeDialog() {
class="text-caption mt-2 mb-2"
>
{{ t("common.last-updated") }}:
{{ new Date(note.updated_at).toLocaleString() }}
{{
new Date(note.updated_at).toLocaleString(
toBrowserLocale(locale),
)
}}
</v-card-subtitle>
</v-expansion-panel-text>
</v-expansion-panel>

View File

@@ -3,6 +3,7 @@ import { useLocalStorage } from "@vueuse/core";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { computed, inject } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import AdminMenu from "@/components/common/Game/AdminMenu.vue";
import PlayBtn from "@/components/common/Game/PlayBtn.vue";
@@ -17,7 +18,14 @@ import storeDownload from "@/stores/download";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { formatBytes, languageToEmoji, regionToEmoji } from "@/utils";
import {
formatBytes,
languageToEmoji,
regionToEmoji,
toBrowserLocale,
} from "@/utils";
const { locale } = useI18n();
withDefaults(
defineProps<{
@@ -330,18 +338,21 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
</div>
<div class="game-list-table-cell d-table-cell px-4">
<span v-if="item.created_at" class="text-no-wrap">{{
new Date(item.created_at).toLocaleDateString("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
})
new Date(item.created_at).toLocaleDateString(
toBrowserLocale(locale),
{
day: "2-digit",
month: "short",
year: "numeric",
},
)
}}</span>
<span v-else>-</span>
</div>
<div class="game-list-table-cell d-table-cell px-4">
<span v-if="item.metadatum.first_release_date" class="text-no-wrap">{{
new Date(item.metadatum.first_release_date).toLocaleDateString(
"en-US",
toBrowserLocale(locale),
{
day: "2-digit",
month: "short",

View File

@@ -23,7 +23,7 @@ import romApi from "@/services/api/rom";
import stateApi from "@/services/api/state";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms from "@/stores/roms";
import { getSupportedEJSCores } from "@/utils";
import { getSupportedEJSCores, toBrowserLocale } from "@/utils";
import {
getMissingCoverImage,
getUnmatchedCoverImage,
@@ -44,7 +44,7 @@ const romsStore = storeRoms();
const heartbeatStore = storeHeartbeat();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { t, locale } = useI18n();
const rom = ref<DetailedRomSchema | null>(null);
const playerState = ref<PlayerState>("loading");
@@ -69,7 +69,7 @@ const releaseDate = computed(() => {
if (!rom.value?.metadatum.first_release_date) return null;
return new Date(
Number(rom.value.metadatum.first_release_date),
).toLocaleDateString("en-US", {
).toLocaleDateString(toBrowserLocale(locale.value), {
day: "2-digit",
month: "short",
year: "numeric",

View File

@@ -91,5 +91,15 @@
"unmatch": "Odpojit ROM",
"unmatch-success": "ROM úspěšně odpojena",
"unselect-all": "Zrušit výběr všeho",
"update-success": "ROM úspěšně aktualizována!"
"update-success": "ROM úspěšně aktualizována!",
"save-data": "Uložená data",
"status-backlogged": "Odloženo",
"status-now-playing": "Právě hraji",
"status-incomplete": "Nedokončeno",
"status-finished": "Dokončeno",
"status-completed-100": "Dokončeno na 100%",
"status-retired": "Opuštěno",
"status-never-playing": "Nikdy nebudu hrát",
"status-hidden": "Skryté",
"updated": "Aktualizováno"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Zuweisung aufheben",
"unmatch-success": "ROM-Zuweisung erfolgreich aufgehoben",
"unselect-all": "Alles abwählen",
"update-success": "ROM erfolgreich aktualisiert!"
"update-success": "ROM erfolgreich aktualisiert!",
"save-data": "Speicherdaten",
"status-backlogged": "Vorgemerkt",
"status-now-playing": "Wird gespielt",
"status-incomplete": "Unvollständig",
"status-finished": "Beendet",
"status-completed-100": "100% abgeschlossen",
"status-retired": "Aufgegeben",
"status-never-playing": "Nie spielen",
"status-hidden": "Versteckt",
"updated": "Aktualisiert"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Unmatch ROM",
"unmatch-success": "ROM unmatched successfully",
"unselect-all": "Unselect all",
"update-success": "ROM updated successfully!"
"update-success": "ROM updated successfully!",
"save-data": "Save data",
"status-backlogged": "Backlogged",
"status-now-playing": "Now Playing",
"status-incomplete": "Incomplete",
"status-finished": "Finished",
"status-completed-100": "Completed 100%",
"status-retired": "Retired",
"status-never-playing": "Never Playing",
"status-hidden": "Hidden",
"updated": "Updated"
}

View File

@@ -91,5 +91,15 @@
"community-notes": "Community Notes",
"confirm-delete-note": "Are you sure you want to delete the note \"{title}\"?",
"unmatch-success": "ROM unmatched successfully",
"update-success": "ROM updated successfully!"
"update-success": "ROM updated successfully!",
"save-data": "Save data",
"status-backlogged": "Backlogged",
"status-now-playing": "Now Playing",
"status-incomplete": "Incomplete",
"status-finished": "Finished",
"status-completed-100": "Completed 100%",
"status-retired": "Retired",
"status-never-playing": "Never Playing",
"status-hidden": "Hidden",
"updated": "Updated"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Desvincular ROM",
"unmatch-success": "ROM desvinculada con éxito",
"unselect-all": "Deseleccionar todos",
"update-success": "¡ROM actualizada con éxito!"
"update-success": "¡ROM actualizada con éxito!",
"save-data": "Datos guardados",
"status-backlogged": "Pendiente",
"status-now-playing": "Jugando",
"status-incomplete": "Incompleto",
"status-finished": "Terminado",
"status-completed-100": "Completado 100%",
"status-retired": "Abandonado",
"status-never-playing": "Nunca jugar",
"status-hidden": "Oculto",
"updated": "Actualizado"
}

View File

@@ -13,7 +13,7 @@
"collections": "Collections",
"community-notes": "Notes de la Communauté",
"companies": "Entreprises",
"completion": "Achèvement",
"completion": "Complétion",
"completionist": "Complétionniste",
"confirm-delete-note": "Êtes-vous sûr de vouloir supprimer la note \"{title}\" ?",
"copy-link": "Copier le lien de téléchargement",
@@ -62,18 +62,18 @@
"note-content": "Contenu de la Note",
"note-title": "Titre de la Note",
"note-title-exists": "Une note avec ce titre existe déjà",
"now-playing": "En cours de lecture",
"now-playing": "En train de jouer",
"personal": "Personnel",
"private": "Privée",
"public": "Publique",
"public-notes": "Notes publiques",
"rating": "Évaluation",
"rating": "Note",
"refresh-metadata": "Actualiser les métadonnées",
"regions": "Régions",
"related-content": "Contenu connexe",
"remove-from-collection": "Retirer de la collection",
"remove-from-favorites": "Retirer des favoris",
"remove-from-playing": "Retirer de jouant",
"remove-from-playing": "Retirer des jeux en cours",
"removing-from-collection": "Retrait de {n} ROMs de la collection",
"removing-title": "Suppression de {n} jeu de RomM | Suppression de {n} jeux de RomM",
"rename-file-details": "Le fichier sera renommé de {from} à {to}. Les tags du nom de fichier ne seront pas affectés.",
@@ -91,5 +91,15 @@
"unmatch": "Dissocier le ROM",
"unmatch-success": "ROM dissociée avec succès",
"unselect-all": "Désélectionner tous",
"update-success": "ROM mise à jour avec succès !"
"update-success": "ROM mise à jour avec succès !",
"save-data": "Sauvegardes",
"status-backlogged": "En attente",
"status-now-playing": "En train de jouer",
"status-incomplete": "Incomplet",
"status-finished": "Terminé",
"status-completed-100": "Complété à 100%",
"status-retired": "Abandonné",
"status-never-playing": "Jamais joué",
"status-hidden": "Caché",
"updated": "Mis à jour"
}

View File

@@ -81,7 +81,7 @@
"show-stats": "Afficher les statistiques",
"show-stats-desc": "Afficher le résumé des statistiques sur la page d'accueil",
"show-status": "Afficher le statut",
"show-status-desc": "Afficher les icônes de statut dans la galerie (en attente, en cours de lecture, terminé, etc.)",
"show-status-desc": "Afficher les icônes de statut dans la galerie (en attente, en train de jouer, terminé, etc.)",
"show-virtual-collections": "Afficher les collections générées automatiquement",
"show-virtual-collections-desc": "Affiché sur la page d'accueil et dans la barre latérale des collections.",
"stopped": "Arrêté",

View File

@@ -91,5 +91,15 @@
"unmatch": "Nem egyező ROM",
"unmatch-success": "A ROM sikeresen nem egyeztetettként lett jelölve",
"unselect-all": "Minden kijelölés visszavonása",
"update-success": "A ROM frissítése sikeresen megtörtént!"
"update-success": "A ROM frissítése sikeresen megtörtént!",
"save-data": "Mentések",
"status-backlogged": "Függőben",
"status-now-playing": "Most játszom",
"status-incomplete": "Befejezetlen",
"status-finished": "Befejezett",
"status-completed-100": "100%-ban teljesített",
"status-retired": "Félretett",
"status-never-playing": "Soha nem játszom",
"status-hidden": "Rejtett",
"updated": "Frissítve"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Rimuovi associazione rom",
"unmatch-success": "Associazione ROM rimossa con successo",
"unselect-all": "Deseleziona tutto",
"update-success": "ROM aggiornata con successo!"
"update-success": "ROM aggiornata con successo!",
"save-data": "Dati di salvataggio",
"status-backlogged": "In attesa",
"status-now-playing": "In gioco",
"status-incomplete": "Incompleto",
"status-finished": "Finito",
"status-completed-100": "Completato 100%",
"status-retired": "Abbandonato",
"status-never-playing": "Mai giocare",
"status-hidden": "Nascosto",
"updated": "Aggiornato"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "検索結果無し",
"unmatch-success": "ROMの関連付けが正常に解除されました",
"unselect-all": "すべて選択解除",
"update-success": "ROMが正常に更新されました"
"update-success": "ROMが正常に更新されました",
"save-data": "セーブデータ",
"status-backlogged": "未処理",
"status-now-playing": "プレイ中",
"status-incomplete": "未完了",
"status-finished": "クリア済み",
"status-completed-100": "100%完了",
"status-retired": "中断",
"status-never-playing": "プレイしない",
"status-hidden": "非表示",
"updated": "更新日時"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "DB 대응 해제",
"unmatch-success": "ROM 연결이 성공적으로 해제되었습니다",
"unselect-all": "모두 선택 해제",
"update-success": "ROM이 성공적으로 업데이트되었습니다!"
"update-success": "ROM이 성공적으로 업데이트되었습니다!",
"save-data": "저장 데이터",
"status-backlogged": "나중에 플레이",
"status-now-playing": "현재 플레이 중",
"status-incomplete": "미완료",
"status-finished": "완료",
"status-completed-100": "100% 완료",
"status-retired": "중단",
"status-never-playing": "플레이 안 함",
"status-hidden": "숨김",
"updated": "업데이트"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Odłącz ROM",
"unmatch-success": "ROM został pomyślnie odłączony",
"unselect-all": "Odznacz wszystkie",
"update-success": "ROM został pomyślnie zaktualizowany!"
"update-success": "ROM został pomyślnie zaktualizowany!",
"save-data": "Dane zapisu",
"status-backlogged": "Zaległe",
"status-now-playing": "Aktualnie grane",
"status-incomplete": "Nieukończone",
"status-finished": "Ukończone",
"status-completed-100": "Ukończone w 100%",
"status-retired": "Porzucone",
"status-never-playing": "Nigdy nie gram",
"status-hidden": "Ukryte",
"updated": "Zaktualizowano"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Desvincular ROM",
"unmatch-success": "ROM desvinculada com sucesso",
"unselect-all": "Desselecionar todos",
"update-success": "ROM atualizada com sucesso!"
"update-success": "ROM atualizada com sucesso!",
"save-data": "Dados salvos",
"status-backlogged": "Em espera",
"status-now-playing": "Jogando agora",
"status-incomplete": "Incompleto",
"status-finished": "Terminado",
"status-completed-100": "Completado 100%",
"status-retired": "Abandonado",
"status-never-playing": "Nunca jogar",
"status-hidden": "Oculto",
"updated": "Atualizado"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Dissociază ROM-ul",
"unmatch-success": "ROM-ul a fost disociat cu succes",
"unselect-all": "Deselectează toate",
"update-success": "ROM-ul a fost actualizat cu succes!"
"update-success": "ROM-ul a fost actualizat cu succes!",
"save-data": "Date salvate",
"status-backlogged": "În așteptare",
"status-now-playing": "Joc acum",
"status-incomplete": "Incomplet",
"status-finished": "Terminat",
"status-completed-100": "Completat 100%",
"status-retired": "Abandonat",
"status-never-playing": "Nu voi juca niciodată",
"status-hidden": "Ascuns",
"updated": "Actualizat"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "Отменить совпадение",
"unmatch-success": "Связь с ROM успешно отменена",
"unselect-all": "Отменить выбор всех",
"update-success": "ROM успешно обновлён!"
"update-success": "ROM успешно обновлён!",
"save-data": "Сохранения",
"status-backlogged": "Отложено",
"status-now-playing": "Сейчас играю",
"status-incomplete": "Не завершено",
"status-finished": "Пройдено",
"status-completed-100": "Пройдено на 100%",
"status-retired": "Заброшено",
"status-never-playing": "Никогда не играть",
"status-hidden": "Скрыто",
"updated": "Обновлено"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "未匹配的 ROM",
"unmatch-success": "ROM 取消匹配成功",
"unselect-all": "取消选择所有",
"update-success": "ROM 更新成功!"
"update-success": "ROM 更新成功!",
"save-data": "存档数据",
"status-backlogged": "待办",
"status-now-playing": "正在游玩",
"status-incomplete": "未完成",
"status-finished": "已通关",
"status-completed-100": "100%完成",
"status-retired": "已搁置",
"status-never-playing": "不玩",
"status-hidden": "隐藏",
"updated": "更新时间"
}

View File

@@ -91,5 +91,15 @@
"unmatch": "清除匹配資料",
"unmatch-success": "ROM 取消匹配成功",
"unselect-all": "取消選擇所有",
"update-success": "ROM 更新成功!"
"update-success": "ROM 更新成功!",
"save-data": "存檔資料",
"status-backlogged": "待遊玩",
"status-now-playing": "正在遊玩",
"status-incomplete": "未完成",
"status-finished": "已破關",
"status-completed-100": "100%完成",
"status-retired": "已擱置",
"status-never-playing": "不玩",
"status-hidden": "隱藏",
"updated": "更新時間"
}

View File

@@ -152,17 +152,31 @@ export function formatBytes(bytes: number, decimals = 2) {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
/**
* Convert locale format from app format (fr_FR) to browser format (fr-FR).
*
* @param locale The locale string (e.g., "fr_FR" or "fr-FR").
* @returns The browser-compatible locale string (e.g., "fr-FR").
*/
export function toBrowserLocale(locale: string): string {
return locale.replace("_", "-");
}
/**
* Format a timestamp to a human-readable string.
*
* @param timestamp The timestamp to format.
* @param locale The locale to use for formatting (e.g., "fr_FR" or "fr-FR"). Defaults to "en-US".
* @returns The formatted timestamp.
*/
export function formatTimestamp(timestamp: string | null) {
export function formatTimestamp(
timestamp: string | null,
locale: string = "en-US",
) {
if (!timestamp) return "-";
const date = new Date(timestamp);
return date.toLocaleString("en-US");
return date.toLocaleString(toBrowserLocale(locale));
}
/**
@@ -606,20 +620,40 @@ export function isRuffleEmulationSupported(
type PlayingStatus = RomUserStatus | "backlogged" | "now_playing" | "hidden";
/**
* Map of ROM statuses to their corresponding emoji and text.
* Map of ROM statuses to their corresponding emoji, text, and i18n key.
*/
export const romStatusMap: Record<
PlayingStatus,
{ emoji: string; text: string }
{ emoji: string; text: string; i18nKey: string }
> = {
backlogged: { emoji: "🔜", text: "Backlogged" },
now_playing: { emoji: "🕹️", text: "Now Playing" },
incomplete: { emoji: "🚧", text: "Incomplete" },
finished: { emoji: "🏁", text: "Finished" },
completed_100: { emoji: "💯", text: "Completed 100%" },
retired: { emoji: "🏴", text: "Retired" },
never_playing: { emoji: "🚫", text: "Never Playing" },
hidden: { emoji: "👻", text: "Hidden" },
backlogged: {
emoji: "🔜",
text: "Backlogged",
i18nKey: "rom.status-backlogged",
},
now_playing: {
emoji: "🕹️",
text: "Now Playing",
i18nKey: "rom.status-now-playing",
},
incomplete: {
emoji: "🚧",
text: "Incomplete",
i18nKey: "rom.status-incomplete",
},
finished: { emoji: "🏁", text: "Finished", i18nKey: "rom.status-finished" },
completed_100: {
emoji: "💯",
text: "Completed 100%",
i18nKey: "rom.status-completed-100",
},
retired: { emoji: "🏴", text: "Retired", i18nKey: "rom.status-retired" },
never_playing: {
emoji: "🚫",
text: "Never Playing",
i18nKey: "rom.status-never-playing",
},
hidden: { emoji: "👻", text: "Hidden", i18nKey: "rom.status-hidden" },
};
/**
@@ -649,12 +683,18 @@ export function getEmojiForStatus(status: PlayingStatus) {
* @param status The ROM status.
* @returns The corresponding text.
*/
export function getTextForStatus(status: PlayingStatus) {
if (status) {
return romStatusMap[status].text;
} else {
return null;
}
export function getTextForStatus(status: PlayingStatus): string | null {
return romStatusMap[status]?.text ?? null;
}
/**
* Get the i18n key for a given ROM status.
*
* @param status The ROM status.
* @returns The corresponding i18n key (e.g., "rom.status-backlogged").
*/
export function getI18nKeyForStatus(status: PlayingStatus): string | null {
return romStatusMap[status]?.i18nKey ?? null;
}
/**

View File

@@ -204,7 +204,7 @@ watch(
<v-tab v-if="currentRom.has_manual" value="manual">
{{ t("rom.manual") }}
</v-tab>
<v-tab value="gamedata"> Game data </v-tab>
<v-tab value="gamedata">{{ t("rom.save-data") }}</v-tab>
<v-tab value="personal">
{{ t("rom.personal") }}
</v-tab>

View File

@@ -23,7 +23,7 @@ import Player from "@/views/Player/EmulatorJS/Player.vue";
const EMULATORJS_VERSION = "nightly";
const { t } = useI18n();
const { t, locale } = useI18n();
const { smAndDown } = useDisplay();
const route = useRoute();
const auth = storeAuth();
@@ -330,8 +330,13 @@ onBeforeUnmount(async () => {
<v-row no-gutters>
<v-col cols="12">
<v-list-item rounded class="px-1 text-caption">
Updated:
{{ formatTimestamp(selectedState.updated_at) }}
{{ t("rom.updated") }}:
{{
formatTimestamp(
selectedState.updated_at,
locale,
)
}}
<span class="text-grey text-caption"
>({{
formatRelativeDate(
@@ -417,8 +422,13 @@ onBeforeUnmount(async () => {
<v-row no-gutters>
<v-col cols="12">
<v-list-item rounded class="px-1 text-caption">
Updated:
{{ formatTimestamp(selectedSave.updated_at) }}
{{ t("rom.updated") }}:
{{
formatTimestamp(
selectedSave.updated_at,
locale,
)
}}
<span class="text-grey text-caption"
>({{
formatRelativeDate(selectedSave.updated_at)
@@ -503,7 +513,8 @@ onBeforeUnmount(async () => {
<v-row class="ga-1" no-gutters>
<v-col cols="12">
<v-list-item rounded class="pa-1 text-caption">
Updated: {{ formatTimestamp(state.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(state.updated_at, locale) }}
<span class="ml-1 text-grey text-caption"
>({{
formatRelativeDate(state.updated_at)
@@ -574,7 +585,8 @@ onBeforeUnmount(async () => {
<v-row class="ga-1" no-gutters>
<v-col cols="12">
<v-list-item rounded class="pa-1 text-caption">
Updated: {{ formatTimestamp(save.updated_at) }}
{{ t("rom.updated") }}:
{{ formatTimestamp(save.updated_at, locale) }}
<span class="ml-1 text-grey text-caption"
>({{ formatRelativeDate(save.updated_at) }})</span
>