feat(i18n): unify django api error resolution

This commit is contained in:
2026-03-13 09:16:23 +00:00
parent 9594a8fcb0
commit 80520bad51
4 changed files with 270 additions and 73 deletions

View File

@@ -24,6 +24,15 @@ def lobby_i18n_error_messages() -> dict:
return shared_i18n_catalog().get("backend", {}).get("errors", {})
def resolve_error_key(code: str) -> str:
resolved = lobby_i18n_errors().get(code)
if isinstance(resolved, str) and resolved:
return resolved
LOGGER.warning("i18n error code missing in shared catalog", extra={"code": code})
return code
def _quality_value(language_candidate: str) -> float | None:
for parameter in language_candidate.split(";")[1:]:
key, separator, value = parameter.partition("=")
@@ -78,12 +87,13 @@ def resolve_error_message(*, key: str, locale: str) -> str:
return key
def api_error(request: HttpRequest, *, key: str, status: int) -> JsonResponse:
def api_error(request: HttpRequest, *, code: str, status: int) -> JsonResponse:
locale = resolve_locale(request)
key = resolve_error_key(code)
return JsonResponse(
{
"error": resolve_error_message(key=key, locale=locale),
"error_code": key,
"error_code": code,
"locale": locale,
},
status=status,

View File

@@ -20,7 +20,7 @@ from fupogfakta.models import (
ScoreEvent,
)
from .i18n import api_error, lobby_i18n_errors
from .i18n import api_error
SESSION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
SESSION_CODE_LENGTH = 6
@@ -31,7 +31,7 @@ JOINABLE_STATUSES = {
GameSession.Status.GUESS,
GameSession.Status.REVEAL,
}
ERROR_CODES = lobby_i18n_errors()
def _json_body(request: HttpRequest) -> dict:
@@ -129,14 +129,14 @@ def join_session(request: HttpRequest) -> JsonResponse:
if not code:
return api_error(
request,
key=ERROR_CODES.get("session_code_required", "session_code_required"),
code="session_code_required",
status=400,
)
if len(nickname) < 2 or len(nickname) > 40:
return api_error(
request,
key=ERROR_CODES.get("nickname_invalid", "nickname_invalid"),
code="nickname_invalid",
status=400,
)
@@ -145,21 +145,21 @@ def join_session(request: HttpRequest) -> JsonResponse:
except GameSession.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("session_not_found", "session_not_found"),
code="session_not_found",
status=404,
)
if session.status not in JOINABLE_STATUSES:
return api_error(
request,
key=ERROR_CODES.get("session_not_joinable", "session_not_joinable"),
code="session_not_joinable",
status=400,
)
if Player.objects.filter(session=session, nickname__iexact=nickname).exists():
return api_error(
request,
key=ERROR_CODES.get("nickname_taken", "nickname_taken"),
code="nickname_taken",
status=409,
)
@@ -191,7 +191,7 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse:
except GameSession.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("session_not_found", "session_not_found"),
code="session_not_found",
status=404,
)
@@ -252,7 +252,7 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
if not category_slug:
return api_error(
request,
key=ERROR_CODES.get("category_slug_required", "category_slug_required"),
code="category_slug_required",
status=400,
)
@@ -263,21 +263,21 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
except GameSession.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("session_not_found", "session_not_found"),
code="session_not_found",
status=404,
)
if session.host_id != request.user.id:
return api_error(
request,
key=ERROR_CODES.get("host_only_start_round", "host_only_start_round"),
code="host_only_start_round",
status=403,
)
if session.status != GameSession.Status.LOBBY:
return api_error(
request,
key=ERROR_CODES.get("round_start_invalid_phase", "round_start_invalid_phase"),
code="round_start_invalid_phase",
status=400,
)
@@ -286,14 +286,14 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
except Category.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("category_not_found", "category_not_found"),
code="category_not_found",
status=404,
)
if not Question.objects.filter(category=category, is_active=True).exists():
return api_error(
request,
key=ERROR_CODES.get("category_has_no_questions", "category_has_no_questions"),
code="category_has_no_questions",
status=400,
)
@@ -302,7 +302,7 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
if session.status != GameSession.Status.LOBBY:
return api_error(
request,
key=ERROR_CODES.get("round_start_invalid_phase", "round_start_invalid_phase"),
code="round_start_invalid_phase",
status=400,
)
@@ -314,7 +314,7 @@ def start_round(request: HttpRequest, code: str) -> JsonResponse:
if not created:
return api_error(
request,
key=ERROR_CODES.get("round_already_configured", "round_already_configured"),
code="round_already_configured",
status=409,
)
@@ -350,21 +350,21 @@ def show_question(request: HttpRequest, code: str) -> JsonResponse:
except GameSession.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("session_not_found", "session_not_found"),
code="session_not_found",
status=404,
)
if session.host_id != request.user.id:
return api_error(
request,
key=ERROR_CODES.get("host_only_show_question", "host_only_show_question"),
code="host_only_show_question",
status=403,
)
if session.status != GameSession.Status.LIE:
return api_error(
request,
key=ERROR_CODES.get("show_question_invalid_phase", "show_question_invalid_phase"),
code="show_question_invalid_phase",
status=400,
)
@@ -373,14 +373,14 @@ def show_question(request: HttpRequest, code: str) -> JsonResponse:
except RoundConfig.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("round_config_missing", "round_config_missing"),
code="round_config_missing",
status=400,
)
if RoundQuestion.objects.filter(session=session, round_number=session.current_round).exists():
return api_error(
request,
key=ERROR_CODES.get("question_already_shown", "question_already_shown"),
code="question_already_shown",
status=409,
)
@@ -393,7 +393,7 @@ def show_question(request: HttpRequest, code: str) -> JsonResponse:
if not available_questions.exists():
return api_error(
request,
key=ERROR_CODES.get("no_available_questions", "no_available_questions"),
code="no_available_questions",
status=400,
)
@@ -434,29 +434,29 @@ def submit_lie(request: HttpRequest, code: str, round_question_id: int) -> JsonR
lie_text = str(payload.get("text", "")).strip()
if not player_id:
return JsonResponse({"error": "player_id is required"}, status=400)
return api_error(request, code="player_id_required", status=400)
if not session_token:
return JsonResponse({"error": "session_token is required"}, status=400)
return api_error(request, code="session_token_required", status=400)
if not lie_text or len(lie_text) > 255:
return JsonResponse({"error": "text must be between 1 and 255 characters"}, status=400)
return api_error(request, code="lie_text_invalid", status=400)
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.status != GameSession.Status.LIE:
return JsonResponse({"error": "Lie submission is only allowed in lie phase"}, status=400)
return api_error(request, code="lie_submission_invalid_phase", status=400)
try:
player = Player.objects.get(pk=player_id, session=session)
except Player.DoesNotExist:
return JsonResponse({"error": "Player not found in session"}, status=404)
return api_error(request, code="player_not_found_in_session", status=404)
if player.session_token != session_token:
return JsonResponse({"error": "Invalid player session token"}, status=403)
return api_error(request, code="invalid_player_session_token", status=403)
try:
round_question = RoundQuestion.objects.get(
@@ -465,21 +465,21 @@ def submit_lie(request: HttpRequest, code: str, round_question_id: int) -> JsonR
round_number=session.current_round,
)
except RoundQuestion.DoesNotExist:
return JsonResponse({"error": "Round question not found"}, status=404)
return api_error(request, code="round_question_not_found", status=404)
try:
round_config = RoundConfig.objects.get(session=session, number=round_question.round_number)
except RoundConfig.DoesNotExist:
return JsonResponse({"error": "Round config missing"}, status=400)
return api_error(request, code="round_config_missing", status=400)
lie_deadline_at = round_question.shown_at + timedelta(seconds=round_config.lie_seconds)
if timezone.now() > lie_deadline_at:
return JsonResponse({"error": "Lie submission window has closed"}, status=400)
return api_error(request, code="lie_submission_closed", status=400)
try:
lie = LieAnswer.objects.create(round_question=round_question, player=player, text=lie_text)
except IntegrityError:
return JsonResponse({"error": "Lie already submitted for this player"}, status=409)
return api_error(request, code="lie_already_submitted", status=409)
return JsonResponse(
{
@@ -507,21 +507,21 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
except GameSession.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("session_not_found", "session_not_found"),
code="session_not_found",
status=404,
)
if session.host_id != request.user.id:
return api_error(
request,
key=ERROR_CODES.get("host_only_mix_answers", "host_only_mix_answers"),
code="host_only_mix_answers",
status=403,
)
if session.status not in {GameSession.Status.LIE, GameSession.Status.GUESS}:
return api_error(
request,
key=ERROR_CODES.get("mix_answers_invalid_phase", "mix_answers_invalid_phase"),
code="mix_answers_invalid_phase",
status=400,
)
@@ -534,7 +534,7 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
except RoundQuestion.DoesNotExist:
return api_error(
request,
key=ERROR_CODES.get("round_question_not_found", "round_question_not_found"),
code="round_question_not_found",
status=404,
)
@@ -543,7 +543,7 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
if locked_session.status not in {GameSession.Status.LIE, GameSession.Status.GUESS}:
return api_error(
request,
key=ERROR_CODES.get("mix_answers_invalid_phase", "mix_answers_invalid_phase"),
code="mix_answers_invalid_phase",
status=400,
)
@@ -563,7 +563,7 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
if len(deduped_answers) < 2:
return api_error(
request,
key=ERROR_CODES.get("not_enough_answers_to_mix", "not_enough_answers_to_mix"),
code="not_enough_answers_to_mix",
status=400,
)
@@ -601,29 +601,29 @@ def submit_guess(request: HttpRequest, code: str, round_question_id: int) -> Jso
selected_text = str(payload.get("selected_text", "")).strip()
if not player_id:
return JsonResponse({"error": "player_id is required"}, status=400)
return api_error(request, code="player_id_required", status=400)
if not session_token:
return JsonResponse({"error": "session_token is required"}, status=400)
return api_error(request, code="session_token_required", status=400)
if not selected_text or len(selected_text) > 255:
return JsonResponse({"error": "selected_text must be between 1 and 255 characters"}, status=400)
return api_error(request, code="selected_text_invalid", status=400)
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.status != GameSession.Status.GUESS:
return JsonResponse({"error": "Guess submission is only allowed in guess phase"}, status=400)
return api_error(request, code="guess_submission_invalid_phase", status=400)
try:
player = Player.objects.get(pk=player_id, session=session)
except Player.DoesNotExist:
return JsonResponse({"error": "Player not found in session"}, status=404)
return api_error(request, code="player_not_found_in_session", status=404)
if player.session_token != session_token:
return JsonResponse({"error": "Invalid player session token"}, status=403)
return api_error(request, code="invalid_player_session_token", status=403)
try:
round_question = RoundQuestion.objects.get(
@@ -632,18 +632,18 @@ def submit_guess(request: HttpRequest, code: str, round_question_id: int) -> Jso
round_number=session.current_round,
)
except RoundQuestion.DoesNotExist:
return JsonResponse({"error": "Round question not found"}, status=404)
return api_error(request, code="round_question_not_found", status=404)
try:
round_config = RoundConfig.objects.get(session=session, number=round_question.round_number)
except RoundConfig.DoesNotExist:
return JsonResponse({"error": "Round config missing"}, status=400)
return api_error(request, code="round_config_missing", status=400)
guess_deadline_at = round_question.shown_at + timedelta(
seconds=round_config.lie_seconds + round_config.guess_seconds
)
if timezone.now() > guess_deadline_at:
return JsonResponse({"error": "Guess submission window has closed"}, status=400)
return api_error(request, code="guess_submission_closed", status=400)
allowed_answers = {
round_question.correct_answer.strip().casefold(),
@@ -656,7 +656,7 @@ def submit_guess(request: HttpRequest, code: str, round_question_id: int) -> Jso
selected_normalized = selected_text.casefold()
if selected_normalized not in allowed_answers:
return JsonResponse({"error": "Selected answer is not part of this round"}, status=400)
return api_error(request, code="selected_answer_invalid", status=400)
correct_normalized = round_question.correct_answer.strip().casefold()
fooled_player_id = None
@@ -674,7 +674,7 @@ def submit_guess(request: HttpRequest, code: str, round_question_id: int) -> Jso
fooled_player_id=fooled_player_id,
)
except IntegrityError:
return JsonResponse({"error": "Guess already submitted for this player"}, status=409)
return api_error(request, code="guess_already_submitted", status=409)
return JsonResponse(
{
@@ -705,13 +705,13 @@ def reveal_scoreboard(request: HttpRequest, code: str) -> JsonResponse:
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.host_id != request.user.id:
return JsonResponse({"error": "Only host can view scoreboard"}, status=403)
return api_error(request, code="host_only_view_scoreboard", status=403)
if session.status != GameSession.Status.REVEAL:
return JsonResponse({"error": "Scoreboard is only available in reveal phase"}, status=400)
return api_error(request, code="scoreboard_invalid_phase", status=400)
leaderboard = list(
Player.objects.filter(session=session)
@@ -739,15 +739,15 @@ def start_next_round(request: HttpRequest, code: str) -> JsonResponse:
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.host_id != request.user.id:
return JsonResponse({"error": "Only host can start next round"}, status=403)
return api_error(request, code="host_only_start_next_round", status=403)
with transaction.atomic():
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
if locked_session.status != GameSession.Status.REVEAL:
return JsonResponse({"error": "Next round can only start from reveal phase"}, status=400)
return api_error(request, code="next_round_invalid_phase", status=400)
locked_session.current_round += 1
locked_session.status = GameSession.Status.LOBBY
@@ -771,15 +771,15 @@ def finish_game(request: HttpRequest, code: str) -> JsonResponse:
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.host_id != request.user.id:
return JsonResponse({"error": "Only host can finish game"}, status=403)
return api_error(request, code="host_only_finish_game", status=403)
with transaction.atomic():
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
if locked_session.status != GameSession.Status.REVEAL:
return JsonResponse({"error": "Game can only be finished from reveal phase"}, status=400)
return api_error(request, code="finish_game_invalid_phase", status=400)
locked_session.status = GameSession.Status.FINISHED
locked_session.save(update_fields=["status"])
@@ -813,20 +813,20 @@ def calculate_scores(request: HttpRequest, code: str, round_question_id: int) ->
try:
session = GameSession.objects.get(code=session_code)
except GameSession.DoesNotExist:
return JsonResponse({"error": "Session not found"}, status=404)
return api_error(request, code="session_not_found", status=404)
if session.host_id != request.user.id:
return JsonResponse({"error": "Only host can calculate scores"}, status=403)
return api_error(request, code="host_only_calculate_scores", status=403)
already_calculated = ScoreEvent.objects.filter(
session=session,
meta__round_question_id=round_question_id,
).exists()
if already_calculated:
return JsonResponse({"error": "Scores already calculated for this round question"}, status=409)
return api_error(request, code="scores_already_calculated", status=409)
if session.status != GameSession.Status.GUESS:
return JsonResponse({"error": "Scores can only be calculated in guess phase"}, status=400)
return api_error(request, code="calculate_scores_invalid_phase", status=400)
try:
round_question = RoundQuestion.objects.get(
@@ -835,16 +835,16 @@ def calculate_scores(request: HttpRequest, code: str, round_question_id: int) ->
round_number=session.current_round,
)
except RoundQuestion.DoesNotExist:
return JsonResponse({"error": "Round question not found"}, status=404)
return api_error(request, code="round_question_not_found", status=404)
try:
round_config = RoundConfig.objects.get(session=session, number=round_question.round_number)
except RoundConfig.DoesNotExist:
return JsonResponse({"error": "Round config missing"}, status=400)
return api_error(request, code="round_config_missing", status=400)
guesses = list(round_question.guesses.select_related("player"))
if not guesses:
return JsonResponse({"error": "No guesses submitted for this round question"}, status=400)
return api_error(request, code="no_guesses_submitted", status=400)
bluff_counts = {}
for guess in guesses:
@@ -854,7 +854,7 @@ def calculate_scores(request: HttpRequest, code: str, round_question_id: int) ->
with transaction.atomic():
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
if locked_session.status != GameSession.Status.GUESS:
return JsonResponse({"error": "Scores can only be calculated in guess phase"}, status=400)
return api_error(request, code="calculate_scores_invalid_phase", status=400)
score_events = []

View File

@@ -1,5 +1,8 @@
{
"locales": ["en", "da"],
"locales": [
"en",
"da"
],
"frontend_error_keys": [
"join_failed",
"nickname_invalid",
@@ -11,50 +14,96 @@
"unknown"
],
"backend_error_codes": [
"calculate_scores_invalid_phase",
"category_has_no_questions",
"category_not_found",
"category_slug_required",
"finish_game_invalid_phase",
"guess_already_submitted",
"guess_submission_closed",
"guess_submission_invalid_phase",
"host_only_calculate_scores",
"host_only_finish_game",
"host_only_mix_answers",
"host_only_show_question",
"host_only_start_next_round",
"host_only_start_round",
"host_only_view_scoreboard",
"invalid_player_session_token",
"lie_already_submitted",
"lie_submission_closed",
"lie_submission_invalid_phase",
"lie_text_invalid",
"mix_answers_invalid_phase",
"next_round_invalid_phase",
"nickname_invalid",
"nickname_taken",
"no_available_questions",
"no_guesses_submitted",
"not_enough_answers_to_mix",
"player_id_required",
"player_not_found_in_session",
"question_already_shown",
"round_already_configured",
"round_config_missing",
"round_question_not_found",
"round_start_invalid_phase",
"scoreboard_invalid_phase",
"scores_already_calculated",
"selected_answer_invalid",
"selected_text_invalid",
"session_code_required",
"session_not_found",
"session_not_joinable",
"session_token_required",
"show_question_invalid_phase"
],
"allowed_contract_only_backend_codes": [
"host_only_action"
],
"backend_error_keys": [
"calculate_scores_invalid_phase",
"category_has_no_questions",
"category_not_found",
"category_slug_required",
"finish_game_invalid_phase",
"guess_already_submitted",
"guess_submission_closed",
"guess_submission_invalid_phase",
"host_only_calculate_scores",
"host_only_finish_game",
"host_only_mix_answers",
"host_only_show_question",
"host_only_start_next_round",
"host_only_start_round",
"host_only_view_scoreboard",
"invalid_player_session_token",
"lie_already_submitted",
"lie_submission_closed",
"lie_submission_invalid_phase",
"lie_text_invalid",
"mix_answers_invalid_phase",
"next_round_invalid_phase",
"nickname_invalid",
"nickname_taken",
"no_available_questions",
"no_guesses_submitted",
"not_enough_answers_to_mix",
"player_id_required",
"player_not_found_in_session",
"question_already_shown",
"round_already_configured",
"round_config_missing",
"round_question_not_found",
"round_start_invalid_phase",
"scoreboard_invalid_phase",
"scores_already_calculated",
"selected_answer_invalid",
"selected_text_invalid",
"session_code_required",
"session_not_found",
"session_not_joinable",
"session_token_required",
"show_question_invalid_phase"
]
}

View File

@@ -296,7 +296,30 @@
"not_enough_answers_to_mix": "not_enough_answers_to_mix",
"host_only_start_round": "host_only_start_round",
"host_only_show_question": "host_only_show_question",
"host_only_mix_answers": "host_only_mix_answers"
"host_only_mix_answers": "host_only_mix_answers",
"player_id_required": "player_id_required",
"session_token_required": "session_token_required",
"lie_text_invalid": "lie_text_invalid",
"player_not_found_in_session": "player_not_found_in_session",
"invalid_player_session_token": "invalid_player_session_token",
"lie_submission_closed": "lie_submission_closed",
"lie_already_submitted": "lie_already_submitted",
"host_only_view_scoreboard": "host_only_view_scoreboard",
"scoreboard_invalid_phase": "scoreboard_invalid_phase",
"host_only_start_next_round": "host_only_start_next_round",
"next_round_invalid_phase": "next_round_invalid_phase",
"host_only_finish_game": "host_only_finish_game",
"finish_game_invalid_phase": "finish_game_invalid_phase",
"host_only_calculate_scores": "host_only_calculate_scores",
"scores_already_calculated": "scores_already_calculated",
"calculate_scores_invalid_phase": "calculate_scores_invalid_phase",
"no_guesses_submitted": "no_guesses_submitted",
"guess_submission_closed": "guess_submission_closed",
"selected_answer_invalid": "selected_answer_invalid",
"guess_already_submitted": "guess_already_submitted",
"lie_submission_invalid_phase": "lie_submission_invalid_phase",
"selected_text_invalid": "selected_text_invalid",
"guess_submission_invalid_phase": "guess_submission_invalid_phase"
},
"errors": {
"session_code_required": {
@@ -378,6 +401,98 @@
"host_only_mix_answers": {
"en": "Only host can mix answers",
"da": "Kun værten kan blande svar"
},
"player_id_required": {
"en": "player_id is required",
"da": "player_id er påkrævet"
},
"session_token_required": {
"en": "session_token is required",
"da": "session_token er påkrævet"
},
"lie_text_invalid": {
"en": "text must be between 1 and 255 characters",
"da": "text skal være mellem 1 og 255 tegn"
},
"player_not_found_in_session": {
"en": "Player not found in session",
"da": "Spiller blev ikke fundet i sessionen"
},
"invalid_player_session_token": {
"en": "Invalid player session token",
"da": "Ugyldigt spiller-session-token"
},
"lie_submission_closed": {
"en": "Lie submission window has closed",
"da": "Vinduet for løgn-indsendelse er lukket"
},
"lie_already_submitted": {
"en": "Lie already submitted for this player",
"da": "Løgnen er allerede indsendt for denne spiller"
},
"host_only_view_scoreboard": {
"en": "Only host can view scoreboard",
"da": "Kun værten kan se scoreboard"
},
"scoreboard_invalid_phase": {
"en": "Scoreboard is only available in reveal phase",
"da": "Scoreboard er kun tilgængeligt i reveal-fasen"
},
"host_only_start_next_round": {
"en": "Only host can start next round",
"da": "Kun værten kan starte næste runde"
},
"next_round_invalid_phase": {
"en": "Next round can only start from reveal phase",
"da": "Næste runde kan kun startes fra reveal-fasen"
},
"host_only_finish_game": {
"en": "Only host can finish game",
"da": "Kun værten kan afslutte spillet"
},
"finish_game_invalid_phase": {
"en": "Game can only be finished from reveal phase",
"da": "Spillet kan kun afsluttes fra reveal-fasen"
},
"host_only_calculate_scores": {
"en": "Only host can calculate scores",
"da": "Kun værten kan udregne score"
},
"scores_already_calculated": {
"en": "Scores already calculated for this round question",
"da": "Score er allerede udregnet for dette rundespørgsmål"
},
"calculate_scores_invalid_phase": {
"en": "Scores can only be calculated in guess phase",
"da": "Score kan kun udregnes i gættefasen"
},
"no_guesses_submitted": {
"en": "No guesses submitted for this round question",
"da": "Ingen gæt er indsendt for dette rundespørgsmål"
},
"guess_submission_closed": {
"en": "Guess submission window has closed",
"da": "Vinduet for gæt-indsendelse er lukket"
},
"selected_answer_invalid": {
"en": "Selected answer is not part of this round",
"da": "Det valgte svar er ikke en del af denne runde"
},
"guess_already_submitted": {
"en": "Guess already submitted for this player",
"da": "Gættet er allerede indsendt for denne spiller"
},
"lie_submission_invalid_phase": {
"en": "Lie submission is only allowed in lie phase",
"da": "Løgn kan kun indsendes i løgnefasen"
},
"selected_text_invalid": {
"en": "selected_text must be between 1 and 255 characters",
"da": "selected_text skal være mellem 1 og 255 tegn"
},
"guess_submission_invalid_phase": {
"en": "Guess submission is only allowed in guess phase",
"da": "Gæt kan kun indsendes i gættefasen"
}
}
},
@@ -416,7 +531,30 @@
"no_available_questions": "start_round_failed",
"mix_answers_invalid_phase": "start_round_failed",
"round_question_not_found": "start_round_failed",
"not_enough_answers_to_mix": "start_round_failed"
"not_enough_answers_to_mix": "start_round_failed",
"player_id_required": "unknown",
"session_token_required": "unknown",
"lie_text_invalid": "unknown",
"lie_submission_invalid_phase": "unknown",
"player_not_found_in_session": "unknown",
"invalid_player_session_token": "unknown",
"lie_submission_closed": "unknown",
"lie_already_submitted": "unknown",
"selected_text_invalid": "unknown",
"guess_submission_invalid_phase": "unknown",
"guess_submission_closed": "unknown",
"selected_answer_invalid": "unknown",
"guess_already_submitted": "unknown",
"host_only_view_scoreboard": "unknown",
"scoreboard_invalid_phase": "unknown",
"host_only_start_next_round": "unknown",
"next_round_invalid_phase": "unknown",
"host_only_finish_game": "unknown",
"finish_game_invalid_phase": "unknown",
"host_only_calculate_scores": "unknown",
"scores_already_calculated": "unknown",
"calculate_scores_invalid_phase": "unknown",
"no_guesses_submitted": "unknown"
}
}
}