bump version to 0.4.1, sanitize API responses, and update string resources

This commit is contained in:
jonasgaudian
2026-02-13 19:01:53 +01:00
parent 99d379071b
commit 73cb3e1855
9 changed files with 79 additions and 35 deletions

View File

@@ -22,8 +22,8 @@ android {
applicationId = "eu.gaudian.translator"
minSdk = 28
targetSdk = 36
versionCode = 21
versionName = "0.4.0"
versionCode = 22
versionName = "0.4.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -171,22 +171,27 @@ class ApiRequestHandler(private val apiManager: ApiManager, context: Context) {
* Handles:
* 1. Markdown code blocks (```json ... ```)
* 2. Conversational wrapping ("Here is the JSON: ...")
* 3. Raw JSON
* 3. Think blocks (<think>...</think>)
* 4. Raw JSON
*/
private fun extractJsonFromResponse(text: String): String {
val trimmed = text.trim()
// 0. First, strip any <think>...</think> blocks (may contain newlines)
val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
val textWithoutThink = thinkRegex.replace(trimmed, "").trim()
// 1. Try to find content within Markdown code blocks
// This regex looks for ``` (optional json) ... content ... ```
val codeBlockRegex = Regex("```(?:json)?\\s*([\\s\\S]*?)\\s*```", RegexOption.IGNORE_CASE)
val match = codeBlockRegex.find(trimmed)
val match = codeBlockRegex.find(textWithoutThink)
if (match != null) {
return match.groupValues[1].trim()
}
// 2. If no code block is found, try to find the outermost JSON structure (Object or Array)
val firstBrace = trimmed.indexOf('{')
val firstBracket = trimmed.indexOf('[')
val firstBrace = textWithoutThink.indexOf('{')
val firstBracket = textWithoutThink.indexOf('[')
var startIndex = -1
var endIndex = -1
@@ -195,18 +200,18 @@ class ApiRequestHandler(private val apiManager: ApiManager, context: Context) {
// We pick whichever appears first
if (firstBrace != -1 && (firstBracket == -1 || firstBrace < firstBracket)) {
startIndex = firstBrace
endIndex = trimmed.lastIndexOf('}')
endIndex = textWithoutThink.lastIndexOf('}')
} else if (firstBracket != -1) {
startIndex = firstBracket
endIndex = trimmed.lastIndexOf(']')
endIndex = textWithoutThink.lastIndexOf(']')
}
if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
return trimmed.substring(startIndex, endIndex + 1)
return textWithoutThink.substring(startIndex, endIndex + 1)
}
// 3. Fallback: return the original text (parsing will likely fail if it's not valid JSON)
return trimmed
// 3. Fallback: return the text without think blocks (parsing will likely fail if it's not valid JSON)
return textWithoutThink
}
/**
@@ -266,4 +271,4 @@ class ApiResponseException(message: String, cause: Throwable? = null) : Exceptio
/**
* Custom exception for API validation errors.
*/
class ApiValidationException(message: String, cause: Throwable? = null) : Exception(message, cause)
class ApiValidationException(message: String, cause: Throwable? = null) : Exception(message, cause)

View File

@@ -174,15 +174,19 @@ object JsonCleanUtil {
private fun isolateJsonBlock(response: String): String {
// Handle specific non-JSON tokens first
// Strip any <think>...</think> blocks (may contain newlines)
val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
val cleanedResponse = thinkRegex.replace(response, "").trim()
// The rest of the function operates on the cleaned response
val markdownRegex = Regex("```json\\s*([\\s\\S]*?)\\s*```")
val markdownMatch = markdownRegex.find(response)
val markdownMatch = markdownRegex.find(cleanedResponse)
if (markdownMatch != null && markdownMatch.groupValues.size > 1) {
return markdownMatch.groupValues[1]
}
val firstBrace = response.indexOf('{')
val firstBracket = response.indexOf('[')
val firstBrace = cleanedResponse.indexOf('{')
val firstBracket = cleanedResponse.indexOf('[')
val startIndex = when {
firstBrace == -1 -> firstBracket
firstBracket == -1 -> firstBrace
@@ -190,12 +194,12 @@ object JsonCleanUtil {
}
if (startIndex == -1) return ""
val lastBrace = response.lastIndexOf('}')
val lastBracket = response.lastIndexOf(']')
val lastBrace = cleanedResponse.lastIndexOf('}')
val lastBracket = cleanedResponse.lastIndexOf(']')
val endIndex = maxOf(lastBrace, lastBracket)
if (endIndex == -1 || startIndex >= endIndex) return ""
return response.substring(startIndex, endIndex + 1)
return cleanedResponse.substring(startIndex, endIndex + 1)
}
/**
@@ -289,4 +293,4 @@ fun formatJsonForDisplay(json: String): String {
} catch (_: Exception) {
json // Fallback to raw JSON if formatting fails
}
}
}

View File

@@ -537,7 +537,7 @@ class DictionaryViewModel @Inject constructor(
showErrorMessage(
getApplication<Application>().getString(
R.string.text_failed_to_fetch_manifest,
e.message
// e.message Let's not include that part here, since it would show the server IP to the user
))
}
}

View File

@@ -1,13 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string-array name="exercise_example_prompts">Exercise Example Prompts</string-array>
<string-array name="changelog_entries">
<item>Version 0.3.0 \n• CSV-Import für Vokabeln aktiviert\n• Option, für einige unterstützte Sprachen einen Übersetzungsserver statt KI-Modelle zu nutzen\n• UI-Fehlerbehebungen \n• Anzeige der Worthäufigkeit \n• Leistungsoptimierungen \n• Verbesserte Übersetzungen (Deutsch und Portugiesisch)</item>
<item>Version 0.4.0 \n• Wörterbuch-Download hinzugefügt (Beta) \n• Verbesserungen der Benutzeroberfläche \n• Fehlerbehebungen \n• Neugestaltete Vokabelkarte mit verbesserter UI \n• Mehr vorkonfigurierte Anbieter \n• Verbesserte Leistung</item>
</string-array>
<!-- Stable, non-localized keys for dictionary content options. Order must match dictionary_content. -->
<string-array name="dictionary_content_keys">
<item>word_class_gender</item>
@@ -22,7 +19,6 @@
<item>idioms</item>
<item>grammatical_features_prepositions</item>
</string-array>
<string-array name="dictionary_content">
<item>Wortart und Genus (bei Nomen)</item>
<item>Deklination (bei Nomen)</item>
@@ -36,4 +32,22 @@
<item>Redewendungen</item>
<item>Grammatikalische Merkmale (bei Präpositionen)</item>
</string-array>
<string-array name="example_prompts"><item>Alles übersetzen, ohne etwas hinzuzufügen.</item>
<item>Ersetze höfliche Pronomen Sie (formell) durch "du".</item>
<item>Mach es sehr formell.</item>
<item>Mach es informell und füge ein Emoji hinzu.</item>
</string-array>
<string-array name="vocabulary_hints"><item>Grundlegende Begrüßungen</item>
<item>Unregelmäßige Verben</item>
<item>Vokabular am Flughafen</item>
<item>Wie man einen Kaffee bestellt</item>
<item>Idiomatische Ausdrücke</item>
</string-array>
<string-array name="vocabulary_example_prompts"><item>Verwende lateinamerikanisches Spanisch</item>
<item>Vermeide lange Wörter</item>
<item>Vermeide Sätze</item>
<item>Enthält viele Verben und Adjektive</item>
<item>Verwende informelle Sprache</item>
</string-array>
</resources>

View File

@@ -696,7 +696,6 @@
<string name="text_failed_to_delete_some_dictionaries">Einige Wörterbücher konnten nicht gelöscht werden</string>
<string name="text_failed_to_download_dictionary">Fehler beim Herunterladen des Wörterbuchs: %1$s</string>
<string name="text_failed_to_fetch_etymology">Etymologie konnte nicht abgerufen werden</string>
<string name="text_failed_to_fetch_manifest">Fehler beim Abrufen des Manifests: %1$s</string>
<string name="text_finish_video_and_start_exercise">Video beenden und Übung starten</string>
<string name="text_orphaned_file_deleted_successfully">Verwaistes File erfolgreich gelöscht</string>
<string name="text_please_select_a_dictionary_language_first">Wähle zuerst eine Wörterbuch-Sprache aus.</string>
@@ -871,5 +870,9 @@
<string name="label_auto_cycle_dev">Auto Cycle (Dev)</string>
<string name="label_regenerate">Neu generieren</string>
<string name="label_read_aloud">Vorlesen</string>
<string name="text_failed_to_fetch_manifest">Fehler beim Abrufen der Download-Informationen zu verfügbaren Wörterbüchern: %1$s</string>
<string name="text_translation_instructions">Setze ein Modell für die Übersetzung und gib optionale Anweisungen, wie übersetzt werden soll.</string>
<string name="label_all_categories">Alle Kategorien</string>
<string name="text_description_dictionary_prompt">Setze ein Modell zum Generieren von Wörterbuchinhalten und gib optionale Anweisungen.</string>
</resources>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string-array name="exercise_example_prompts">Exercise Example Prompts</string-array>
<!-- Stable, non-localized keys for dictionary content options. Order must match dictionary_content. -->
<string-array name="dictionary_content_keys">
<item>word_class_gender</item>
@@ -16,7 +15,6 @@
<item>idioms</item>
<item>grammatical_features_prepositions</item>
</string-array>
<string-array name="dictionary_content">
<item>Classe da palavra e gênero (para substantivos)</item>
<item>Declinação (para substantivos)</item>
@@ -30,11 +28,26 @@
<item>Expressões idiomáticas</item>
<item>Recursos gramaticais (para preposições)</item>
</string-array>
<string-array name="changelog_entries">
<item>Versão 0.3.0 \n• Habilitada a importação de vocabulário via CSV\n• Opção para usar um servidor de tradução em vez de modelos de IA para alguns idiomas suportados\n• Correções de bugs na interface \n• Exibição da frequência de palavras \n• Otimizações de desempenho \n• Traduções melhoradas (Alemão e Português)</item>
<item>Versão 0.4.0 \n• Adicionado download de dicionário (beta) \n• Melhorias na interface \n• Correções de bugs \n• Cartão de vocabulário redesenhado com interface melhorada \n• Mais provedores pré-configurados \n• Desempenho melhorado</item>
</string-array>
<string-array name="example_prompts"><item>Traduzir tudo sem adicionar mais nada</item>
<item>Usa o pronome de "tu" em vez do formal "você/vos/vous"</item>
<item>Faz tudo muito formal e adicione um ponto final</item>
<item>Faz tudo informal</item>
</string-array>
<string-array name="vocabulary_hints"><item>Saudação básica</item>
<item>Verbos irregulares</item>
<item>Vocabulário no aeroporto</item>
<item>Como pedir um café</item>
<item>Expressões idiomáticas</item>
</string-array>
<string-array name="vocabulary_example_prompts"><item>Usar espanhol latino-americano</item>
<item>Evitar palavras longas</item>
<item>Evitar frases</item>
<item>Incluir muitos verbos e adjetivos</item>
<item>Usar linguagem informal</item>
</string-array>
</resources>

View File

@@ -689,7 +689,6 @@
<string name="text_failed_to_delete_some_dictionaries">Falhou ao excluir alguns dicionários</string>
<string name="text_failed_to_download_dictionary">Falhou ao baixar dicionário: %1$s</string>
<string name="text_failed_to_fetch_etymology">Falhou ao buscar etimologia</string>
<string name="text_failed_to_fetch_manifest">Falhou ao buscar manifesto: %1$s</string>
<string name="text_finish_video_and_start_exercise">Terminar Vídeo e Começar Exercício</string>
<string name="text_hint">Dica</string>
<string name="text_no_data_available">Sem Dados Disponíveis</string>
@@ -705,7 +704,6 @@
<string name="text_delete_vocabulary_item">Excluir Item do Vocabulário?</string>
<string name="text_are_you_sure_delete_vocabulary_item">Tem certeza de que deseja excluir este item do vocabulário?</string>
<string name="label_origin_language">Idioma de Origem</string>
<string name="label_guessing_exercise">Chutando</string>
<string name="label_spelling_exercise">Ortografia</string>
<string name="label_multiple_choice_exercise">Múltipla Escolha</string>
@@ -867,5 +865,12 @@
<string name="label_auto_cycle_dev">Auto Ciclo (Dev)</string>
<string name="label_regenerate">Regenerar</string>
<string name="label_read_aloud">Ler em Voz Alta</string>
<string name="label_language_direction">Direção do idioma</string>
<string name="label_target_language">Idioma de destino</string>
<string name="text_failed_to_fetch_manifest">Falha ao buscar informações de download sobre dicionários disponíveis: %1$s</string>
<string name="text_translation_instructions">Defina o modelo para tradução e dê instruções opcionais sobre como traduzir.</string>
<string name="text_language_direction_explanation">Você pode definir uma preferência opcional sobre qual idioma deve vir primeiro ou segundo.</string>
<string name="label_all_categories">Todas as Categorias</string>
<string name="text_description_dictionary_prompt">Defina um modelo para gerar conteúdo do dicionário e dê instruções opcionais.</string>
</resources>

View File

@@ -818,7 +818,7 @@
<string name="text_failed_to_delete_some_dictionaries">Failed to delete some dictionaries</string>
<string name="text_failed_to_download_dictionary">Failed to download dictionary: %1$s</string>
<string name="text_failed_to_fetch_etymology">Failed to fetch etymology</string>
<string name="text_failed_to_fetch_manifest">Failed to fetch manifest: %1$s</string>
<string name="text_failed_to_fetch_manifest">Failed to fetch download information about available dictionaries: %1$s</string>
<string name="text_failed_to_get_translations">Failed to get translations: %1$s</string>
<string name="text_false">False</string>
<string name="text_favorites">Favorites</string>