this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: issues with h-details-editor

+240 -171
+1 -1
deno.json
··· 1 1 { 2 - "version": "3.5.1", 2 + "version": "3.5.2", 3 3 "workspace": ["./data"], 4 4 "compilerOptions": { 5 5 "lib": [
+12
www/components/h-card-details.ts
··· 30 30 locale = '' 31 31 transliteration = '' 32 32 33 + #onUpdate = () => this.requestUpdate() 34 + 33 35 override createRenderRoot() { 34 36 return this 37 + } 38 + 39 + override connectedCallback() { 40 + super.connectedCallback() 41 + subjects.addEventListener(this.#onUpdate) 42 + } 43 + 44 + override disconnectedCallback() { 45 + super.disconnectedCallback() 46 + subjects.removeEventListener(this.#onUpdate) 35 47 } 36 48 37 49 override render() {
+49 -26
www/components/h-details-editor.ts
··· 28 28 primaryMeaning: '', 29 29 auxMeanings: '', 30 30 } 31 + #stateInitialized = false 32 + 33 + #onUpdate = () => this.requestUpdate() 31 34 32 35 override createRenderRoot() { 33 36 return this 34 37 } 35 38 39 + override connectedCallback() { 40 + this.#stateInitialized = false 41 + super.connectedCallback() 42 + subjects.addEventListener(this.#onUpdate) 43 + app.addEventListener(this.#onUpdate) 44 + } 45 + 46 + override disconnectedCallback() { 47 + super.disconnectedCallback() 48 + subjects.removeEventListener(this.#onUpdate) 49 + app.removeEventListener(this.#onUpdate) 50 + } 51 + 36 52 override willUpdate(changed: Map<string, unknown>) { 37 - if (changed.has('subjectId')) { 38 - const subject = subjects.state.byId[this.subjectId] 39 - if (!subject) return 40 - const { data, id } = subject 41 - this.#state = { 42 - subjectId: id, 43 - meaningHint: data.meaningHint ?? '', 44 - meaningMnemonic: data.meaningMnemonic ?? '', 45 - readingHint: data.readingHint ?? '', 46 - readingMnemonic: data.readingMnemonic ?? '', 47 - primaryMeaning: data.meanings.find((m) => m.isPrimary)?.value || '', 48 - auxMeanings: data.meanings 49 - .filter((m) => !m.isPrimary) 50 - .map((m) => m.value) 51 - .join(', '), 52 - } 53 + if (!changed.has('subjectId') && this.#stateInitialized) return 54 + const subject = subjects.state.byId[this.subjectId] 55 + if (!subject) return 56 + const { data, id } = subject 57 + const o = app.state.overrides?.[id] 58 + this.#state = { 59 + subjectId: id, 60 + meaningHint: o?.meaningHint ?? data.meaningHint ?? '', 61 + meaningMnemonic: o?.meaningMnemonic ?? data.meaningMnemonic ?? '', 62 + readingHint: o?.readingHint ?? data.readingHint ?? '', 63 + readingMnemonic: o?.readingMnemonic ?? data.readingMnemonic ?? '', 64 + primaryMeaning: data.meanings.find((m) => m.isPrimary)?.value || '', 65 + auxMeanings: o?.auxMeanings ?? data.meanings 66 + .filter((m) => !m.isPrimary) 67 + .map((m) => m.value) 68 + .join(', '), 53 69 } 70 + this.#stateInitialized = true 54 71 } 55 72 56 73 override render() { ··· 79 96 <label for="auxMeanings">${getString('synonyms')}</label> 80 97 <input 81 98 name="auxMeanings" 99 + style="width: 100%" 82 100 .value="${this.#state.auxMeanings || ''}" 83 101 @input="${(e: InputEvent) => (this.#state.auxMeanings = ( 84 102 e.target as HTMLInputElement ··· 90 108 <textarea 91 109 name="meaningHint" 92 110 rows="4" 111 + style="width: 100%" 93 112 .value="${this.#state.meaningHint || ''}" 94 113 @input="${(e: InputEvent) => (this.#state.meaningHint = ( 95 114 e.target as HTMLInputElement ··· 101 120 <textarea 102 121 name="meaningMnemonic" 103 122 rows="4" 123 + style="width: 100%" 104 124 .value="${this.#state.meaningMnemonic || ''}" 105 125 @input="${(e: InputEvent) => (this.#state.meaningMnemonic = ( 106 126 e.target as HTMLInputElement 107 127 ).value)}" 108 128 ></textarea> 109 - </div> 110 - <div> 111 - <button type="button" @click="${onSave}">${getString( 112 - 'save', 113 - )}</button> 114 129 </div> 115 130 </details> 116 131 ··· 121 136 <textarea 122 137 name="readingHint" 123 138 rows="4" 139 + style="width: 100%" 124 140 .value="${this.#state.readingHint || ''}" 125 141 @input="${(e: InputEvent) => (this.#state.readingHint = ( 126 142 e.target as HTMLInputElement ··· 132 148 <textarea 133 149 name="readingMnemonic" 134 150 rows="4" 151 + style="width: 100%" 135 152 .value="${this.#state.readingMnemonic || ''}" 136 153 @input="${(e: InputEvent) => (this.#state.readingMnemonic = ( 137 154 e.target as HTMLInputElement 138 155 ).value)}" 139 156 ></textarea> 140 157 </div> 141 - <div> 142 - <button type="button" @click="${onSave}">${getString( 143 - 'save', 144 - )}</button> 145 - </div> 146 158 </details> 159 + 160 + <div> 161 + <button 162 + type="button" 163 + @click="${() => 164 + this.dispatchEvent(new CustomEvent('cancel', { bubbles: true }))}" 165 + > 166 + ${getString('cancel')} 167 + </button> 168 + <button type="button" @click="${onSave}">${getString('save')}</button> 169 + </div> 147 170 </div> 148 171 ` 149 172 }
+1 -32
www/components/h-reference-header.ts
··· 4 4 5 5 /** 6 6 * Header for the reference/subject page. 7 - * Displays back button, edit/cancel toggle, and subject slug. 8 - * Communicates edit state via document events with r-reference-subject. 7 + * Displays back button and subject slug. 9 8 * 10 9 * @element h-reference-header 11 10 */ ··· 15 14 } 16 15 17 16 subjectId = '' 18 - #isEditing = false 19 17 20 18 #onUpdate = () => this.requestUpdate() 21 19 22 - #onEditStateChanged = (e: Event) => { 23 - this.#isEditing = 24 - (e as CustomEvent<{ isEditing: boolean }>).detail.isEditing 25 - this.requestUpdate() 26 - } 27 - 28 20 override createRenderRoot() { 29 21 return this 30 22 } 31 23 32 24 override connectedCallback() { 33 25 super.connectedCallback() 34 - this.#isEditing = false 35 26 subjects.addEventListener(this.#onUpdate) 36 - document.addEventListener( 37 - 'h-reference:edit-state-changed', 38 - this.#onEditStateChanged, 39 - ) 40 27 } 41 28 42 29 override disconnectedCallback() { 43 30 super.disconnectedCallback() 44 31 subjects.removeEventListener(this.#onUpdate) 45 - document.removeEventListener( 46 - 'h-reference:edit-state-changed', 47 - this.#onEditStateChanged, 48 - ) 49 - } 50 - 51 - #handleEditClick() { 52 - document.dispatchEvent(new CustomEvent('h-reference:edit-request')) 53 32 } 54 33 55 34 override render() { ··· 59 38 return html` 60 39 <div class="study-header ${color}-dots"> 61 40 <a class="left ${color}" @click="${() => history.back()}">back</a> 62 - <div class="right"> 63 - ${this.#isEditing 64 - ? html` 65 - <button @click="${this.#handleEditClick}">cancel</button> 66 - ` 67 - : html` 68 - <div @click="${this 69 - .#handleEditClick}"><ui-icon name="edit"></ui-icon></div> 70 - `} 71 - </div> 72 41 <h1 class="${color}">${subject.data.slug}</h1> 73 42 </div> 74 43 `
+6 -9
www/components/h-stats.ts
··· 92 92 93 93 override connectedCallback() { 94 94 super.connectedCallback() 95 - const url = new URL(location.href) 96 - const levelParam = url.searchParams.get('level') 97 - if (levelParam) { 98 - this.#currLevel = Number(levelParam) 99 - url.searchParams.delete('level') 100 - history.replaceState(null, '', url) 95 + const savedLevel = sessionStorage.getItem('stats-level') 96 + if (savedLevel) { 97 + this.#currLevel = Number(savedLevel) 98 + sessionStorage.removeItem('stats-level') 101 99 } else { 102 100 this.#currLevel = app.userLevel || 0 103 101 } ··· 123 121 type="${tileType}" 124 122 state="${state}" 125 123 @click="${() => { 126 - const url = new URL(location.href) 127 - url.searchParams.set('level', String(this.#currLevel)) 128 - history.replaceState(null, '', url) 124 + sessionStorage.setItem('stats-level', String(this.#currLevel)) 125 + history.replaceState(null, '', location.pathname + location.hash) 129 126 location.href = `#!/reference/subject/${subject.id}` 130 127 }}" 131 128 class="${colorClass} font-${app.locale} ${color}-shadow"
+15 -24
www/routes/reference/subject.ts
··· 1 1 import { html, LitElement } from 'lit' 2 2 import app from '$/models/app.ts' 3 + import { subjects } from '$/models/subjects.ts' 3 4 4 5 /** 5 6 * Main content for the reference/subject page. 6 - * Owns the edit state and syncs it to r-reference-header via document events. 7 - * 8 - * Listens for 'r-reference:edit-request' to toggle edit mode. 9 - * Dispatches 'r-reference:edit-state-changed' so r-reference-header can reflect state. 7 + * Owns the edit state; shows h-card-details or h-details-editor. 10 8 * 11 9 * @element r-reference-subject 12 10 */ ··· 20 18 21 19 #onUpdate = () => this.requestUpdate() 22 20 23 - #onEditRequest = () => { 24 - this.#isEditing = !this.#isEditing 25 - document.dispatchEvent( 26 - new CustomEvent('r-reference:edit-state-changed', { 27 - detail: { isEditing: this.#isEditing }, 28 - }), 29 - ) 30 - this.requestUpdate() 31 - } 32 - 33 21 #onDone = () => { 34 22 this.#isEditing = false 35 - document.dispatchEvent( 36 - new CustomEvent('r-reference:edit-state-changed', { 37 - detail: { isEditing: false }, 38 - }), 39 - ) 40 23 this.requestUpdate() 41 24 } 42 25 ··· 48 31 super.connectedCallback() 49 32 this.#isEditing = false 50 33 app.addEventListener(this.#onUpdate) 51 - document.addEventListener('r-reference:edit-request', this.#onEditRequest) 34 + subjects.addEventListener(this.#onUpdate) 52 35 globalThis.scrollTo(0, 0) 53 36 } 54 37 55 38 override disconnectedCallback() { 56 39 super.disconnectedCallback() 57 40 app.removeEventListener(this.#onUpdate) 58 - document.removeEventListener( 59 - 'r-reference:edit-request', 60 - this.#onEditRequest, 61 - ) 41 + subjects.removeEventListener(this.#onUpdate) 62 42 } 63 43 64 44 override render() { ··· 67 47 <h-details-editor 68 48 subject-id="${this.subjectId}" 69 49 @done="${this.#onDone}" 50 + @cancel="${this.#onDone}" 70 51 ></h-details-editor> 71 52 ` 72 53 } ··· 78 59 locale="${app.locale}" 79 60 transliteration="${app.transliteration}" 80 61 ></h-card-details> 62 + <div style="max-width: var(--max-width-sm); padding: var(--s3); margin: auto;"> 63 + <button 64 + @click="${() => { 65 + this.#isEditing = true 66 + this.requestUpdate() 67 + }}" 68 + > 69 + Edit 70 + </button> 71 + </div> 81 72 ` 82 73 } 83 74 }
+10 -4
www/routes/search.ts
··· 56 56 subjects.addEventListener(this.#onUpdate) 57 57 globalThis.scrollTo(0, 0) 58 58 59 - const s = new URLSearchParams(location.search).get('s') 59 + const qIdx = location.hash.indexOf('?') 60 + const s = qIdx >= 0 61 + ? new URLSearchParams(location.hash.slice(qIdx)).get('s') 62 + : null 60 63 if (s) { 61 64 this.#isLoading = true 62 65 this.#text = s ··· 85 88 this.#isLoading = true 86 89 this.#text = value 87 90 this.#updateSubjects() 88 - const url = new URL(location.href) 89 - url.searchParams.set('s', this.#text) 90 - history.replaceState(null, '', url) 91 + const base = location.hash.split('?')[0] 92 + history.replaceState( 93 + null, 94 + '', 95 + `${location.pathname}${base}?s=${encodeURIComponent(this.#text)}`, 96 + ) 91 97 } 92 98 }}" 93 99 />
+53 -21
www/routes/study.ts
··· 44 44 45 45 #total = 0 46 46 #sessionLocale = '' 47 + #isEditing = false 47 48 48 49 // Kept fresh on each render for use in the async submit handler 49 50 #cardType: CardType = Reading ··· 92 93 #handleSubmit = async (e: Event) => { 93 94 const answer = (e as CustomEvent<{ answer: string }>)?.detail?.answer 94 95 if (!answer || !deck) return 96 + this.#isEditing = false 95 97 96 98 const isPending = deck.state.currCardState === Pending 97 99 deck.submit(answer) ··· 297 299 ` 298 300 : null} ${this.mode === Learn || cardState !== Pending 299 301 ? html` 300 - ${keyed( 301 - currSubject?.id + cardType, 302 - html` 303 - <h-card-details 302 + ${this.#isEditing 303 + ? html` 304 + <h-details-editor 304 305 subject-id="${String(currSubject?.id)}" 305 - mode="${this.mode === Learn ? 'learn' : 'quiz'}" 306 - card-type="${cardType === Meaning ? 'meaning' : 'reading'}" 307 - locale="${app.locale}" 308 - transliteration="${app.transliteration}" 309 - ></h-card-details> 310 - `, 311 - )} 306 + @done="${() => { 307 + this.#isEditing = false 308 + this.requestUpdate() 309 + }}" 310 + @cancel="${() => { 311 + this.#isEditing = false 312 + this.requestUpdate() 313 + }}" 314 + ></h-details-editor> 315 + ` 316 + : keyed( 317 + currSubject?.id + cardType, 318 + html` 319 + <h-card-details 320 + subject-id="${String(currSubject?.id)}" 321 + mode="${this.mode === Learn ? 'learn' : 'quiz'}" 322 + card-type="${cardType === Meaning 323 + ? 'meaning' 324 + : 'reading'}" 325 + locale="${app.locale}" 326 + transliteration="${app.transliteration}" 327 + ></h-card-details> 328 + `, 329 + )} 312 330 <div class="study-button"> 313 331 ${this.mode === Learn 314 332 ? html` ··· 327 345 r.isAcceptedAnswer 328 346 )?.value ?? 329 347 subject?.data.readings[0]?.value ?? '') 348 + this.#isEditing = false 330 349 deck.submit(answer) 331 350 app.state.assignments[app.locale] = deck.state 332 351 .assignments as Record<string, Assignment> ··· 337 356 ` 338 357 : null} 339 358 </div> 340 - ${this.mode === Quiz 359 + ${this.mode === Quiz && !this.#isEditing 341 360 ? html` 342 - <button class="redo ${color}-inverse" @click="${() => { 343 - deck.redo() 344 - const el = this.querySelector('h-answer-input') as 345 - | HAnswerInput 346 - | null 347 - el?.clear() 348 - }}"> 349 - ${getString('redo')} 350 - </button> 361 + <div 362 + style="max-width: var(--max-width-sm); padding: var(--s3); margin: auto; display: flex; gap: var(--s2);" 363 + > 364 + <button 365 + class="${color}-inverse" 366 + @click="${() => { 367 + this.#isEditing = true 368 + this.requestUpdate() 369 + }}" 370 + > 371 + Edit 372 + </button> 373 + <button class="redo ${color}-inverse" @click="${() => { 374 + deck.redo() 375 + const el = this.querySelector('h-answer-input') as 376 + | HAnswerInput 377 + | null 378 + el?.clear() 379 + }}"> 380 + ${getString('redo')} 381 + </button> 382 + </div> 351 383 ` 352 384 : null} 353 385 `
+22 -22
www/static/strings/en.json
··· 1 1 { 2 2 "about": "About", 3 + "all_done": "All done!", 3 4 "assignments": "Assignments", 4 5 "back": "Back", 5 6 "cancel": "Cancel", 7 + "card_order": "Card Order", 8 + "card_sort_hint": "How the different meaning and reading flashcards are ordered in a study session.", 9 + "card_sort_method": "Sort Method", 6 10 "character": "Character", 7 11 "characters": "Characters", 12 + "custom_sets": "Custom Sets", 8 13 "daily_limit": "Daily Limit", 9 14 "daily_limit_hint": "The maximum number of items to learn in a day", 15 + "days_in_a_row": "days in a row", 16 + "display_type": "Display Type", 10 17 "download_audio": "Download Audio", 11 18 "downloads": "Downloads", 12 19 "done": "Done", ··· 15 22 "export_progress": "Export Progress", 16 23 "failed": "Failed", 17 24 "filename": "Filename", 18 - "all_done": "All done!", 19 - "display_type": "Display Type", 20 25 "finished": "Finished!", 21 26 "furigana": "Furigana", 22 27 "games": "Games", 28 + "general": "General", 29 + "hant": "Traditional Chinese", 30 + "hanzi_app": "HanziApp", 23 31 "hiragana": "Hiragana", 24 32 "hiragana_game": "Learn Hiragana", 25 - "journal_game": "Daily Journal", 26 33 "hiragana_instructions_daily": "Select 1-2 new sets of ひらがな to learn per day (one at a time!).", 27 34 "hiragana_instructions_words": "It could be helpful to also add the previous day's sets. The practice words will also include all prior sets.", 28 35 "hiragana_repeat_note": "Note: this list will repeat indefinitely", 29 36 "hiragana_words_note": "Here are some words to practice! Don't worry about remembering the meanings or kanji of the words. We are just practicing sounds!", 30 - "instructions": "Instructions", 31 - "play_audio": "Play Audio", 32 - "practice_type": "Practice Type", 33 - "skip_to_words": "Skip to words", 34 - "speaking": "Speaking", 35 - "strokes": "Strokes", 36 - "typing": "Typing", 37 - "writing": "Writing", 38 - "general": "General", 39 - "hanzi_app": "HanziApp", 40 37 "hint": "Hint", 41 38 "hsk": "HSK", 42 39 "import_now": "Import Now", 43 40 "import_progress": "Import Progress", 41 + "instructions": "Instructions", 44 42 "ja": "Japan (Japanese)", 43 + "journal_game": "Daily Journal", 45 44 "kanji_app": "KanjiApp", 46 45 "last_unlocked": "Last Unlocked", 47 46 "learn_group_size": "Learn Group Size", ··· 49 48 "lessons": "Lessons", 50 49 "level": "Level", 51 50 "locale": "Locale", 51 + "longest_streak": "longest streak", 52 52 "lvl": "lvl", 53 53 "mark_known": "Mark as Already Learned", 54 54 "meaning": "Meaning", ··· 56 56 "mnemonic": "Mnemonic", 57 57 "new": "New", 58 58 "no_games": "No games available for this language.", 59 - "days_in_a_row": "days in a row", 60 - "longest_streak": "longest streak", 61 59 "no_new_lessons": "No new lessons", 62 60 "no_new_reviews": "No new reviews", 63 61 "open_source": "Hanzi Offline is Open Source", ··· 66 64 "paired": "Paired", 67 65 "passed": "Passed", 68 66 "play_again": "Play Again", 67 + "play_audio": "Play Audio", 68 + "practice_type": "Practice Type", 69 69 "progress": "Progress", 70 70 "radical": "Radical", 71 71 "radicals": "Radicals", ··· 81 81 "sentences_none": "No sentences available yet. Keep studying to unlock more!", 82 82 "settings": "Settings", 83 83 "show_answer": "Show Answer", 84 + "skip_to_words": "Skip to words", 84 85 "sound": "Sound", 86 + "speaking": "Speaking", 87 + "strokes": "Strokes", 85 88 "studied": "Studied", 86 89 "study": "Study", 87 90 "study_group_size": "Study Group Size", ··· 89 92 "sync": "Sync", 90 93 "sync_url": "Synclink Url", 91 94 "synonyms": "Synonyms", 92 - "card_order": "Card Order", 93 - "custom_sets": "Custom Sets", 94 - "card_sort_method": "Sort Method", 95 - "card_sort_hint": "How the different meaning and reading flashcards are ordered in a study session.", 96 95 "test_connection": "Connect", 97 96 "tocfl": "TOCFL", 98 97 "todays_words": "Today's Words", 98 + "tones": "Tones", 99 99 "total_learned": "Total Learned", 100 + "typing": "Typing", 100 101 "use_app_for_audio": "Get the mobile app to download audio for offline use!", 101 102 "user_language": "Language", 102 103 "vocabulary": "Vocabulary", 104 + "writing": "Writing", 103 105 "zh_CN": "China (Mandarin, Simplified)", 104 106 "zh_HK": "Hong Kong (Cantonese, Traditional)", 105 107 "zh_TW": "Taiwan (Mandarin, Traditional)", ··· 109 111 "zhuyin_instructions_words": "It can help to add the previous day's sets too. Practice words include all prior sets.", 110 112 "zhuyin_repeat_note": "Note: this list will repeat indefinitely", 111 113 "zhuyin_typing_hint": "type pinyin…", 112 - "zhuyin_words_note": "Here are some words to practice! Focus on the sounds—don't worry about memorizing meanings yet.", 113 - "hant": "Traditional Chinese", 114 - "tones": "Tones" 114 + "zhuyin_words_note": "Here are some words to practice! Focus on the sounds—don't worry about memorizing meanings yet." 115 115 }
+70 -31
www/static/strings/es.json
··· 1 1 { 2 2 "about": "Acerca de", 3 + "all_done": "¡Todo listo!", 3 4 "assignments": "Tareas", 4 5 "back": "Atrás", 5 6 "cancel": "Cancelar", 7 + "card_order": "Orden de tarjetas", 8 + "card_sort_hint": "Cómo se ordenan las tarjetas de significado y lectura en una sesión de estudio.", 9 + "card_sort_method": "Método de ordenación", 6 10 "character": "Carácter", 7 11 "characters": "Caracteres", 8 - "daily_limit": "Límite Diario", 12 + "custom_sets": "Conjuntos personalizados", 13 + "daily_limit": "Límite diario", 9 14 "daily_limit_hint": "El número máximo de elementos a aprender en un día", 10 - "download_audio": "Descargar Audio", 15 + "days_in_a_row": "días seguidos", 16 + "display_type": "Tipo de visualización", 17 + "download_audio": "Descargar audio", 11 18 "downloads": "Descargas", 12 - "done": "Listo", 19 + "done": "Hecho", 13 20 "examples": "Ejemplos", 14 21 "explanation": "explicación", 15 - "export_progress": "Exportar Progreso", 22 + "export_progress": "Exportar progreso", 16 23 "failed": "Fallido", 17 - "filename": "Nombre de Archivo", 24 + "filename": "Nombre de archivo", 25 + "finished": "¡Terminado!", 26 + "furigana": "Furigana", 27 + "games": "Juegos", 18 28 "general": "General", 29 + "hant": "Chino tradicional", 19 30 "hanzi_app": "HanziApp", 31 + "hiragana": "Hiragana", 32 + "hiragana_game": "Aprender Hiragana", 33 + "hiragana_instructions_daily": "Selecciona 1-2 nuevos conjuntos de ひらがな para aprender por día (¡de uno en uno!).", 34 + "hiragana_instructions_words": "También puede ser útil añadir los conjuntos del día anterior. Las palabras de práctica también incluirán todos los conjuntos anteriores.", 35 + "hiragana_repeat_note": "Nota: esta lista se repetirá indefinidamente", 36 + "hiragana_words_note": "¡Aquí hay algunas palabras para practicar! No te preocupes por recordar los significados o kanji de las palabras. ¡Solo estamos practicando sonidos!", 20 37 "hint": "Pista", 21 38 "hsk": "HSK", 22 - "import_now": "Importar Ahora", 23 - "import_progress": "Importar Progreso", 24 - "ja": "Japón (Japonés)", 39 + "import_now": "Importar ahora", 40 + "import_progress": "Importar progreso", 41 + "instructions": "Instrucciones", 42 + "ja": "Japón (japonés)", 43 + "journal_game": "Diario personal", 25 44 "kanji_app": "KanjiApp", 26 - "last_unlocked": "Último Desbloqueado", 27 - "learn_group_size": "Tamaño de Grupo de Aprendizaje", 28 - "learn_group_size_hint": "El número máximo de elementos a aprender en una sola sesión de aprendizaje.", 45 + "last_unlocked": "Último desbloqueado", 46 + "learn_group_size": "Tamaño del grupo de aprendizaje", 47 + "learn_group_size_hint": "El número máximo de elementos a aprender en una sesión de aprendizaje.", 29 48 "lessons": "Lecciones", 30 49 "level": "Nivel", 31 - "locale": "Configuración Regional", 32 - "lvl": "niv", 33 - "mark_known": "Marcar como Ya Aprendido", 50 + "locale": "Idioma regional", 51 + "longest_streak": "racha más larga", 52 + "lvl": "nvl", 53 + "mark_known": "Marcar como ya aprendido", 34 54 "meaning": "Significado", 35 - "meaning_first": "Significado Primero", 55 + "meaning_first": "Significado primero", 36 56 "mnemonic": "Mnemónico", 37 57 "new": "Nuevo", 38 - "no_new_lessons": "Sin lecciones nuevas", 39 - "no_new_reviews": "Sin repasos nuevos", 40 - "open_source": "Hanzi Sin Conexión es de Código Abierto", 58 + "no_games": "No hay juegos disponibles para este idioma.", 59 + "no_new_lessons": "No hay lecciones nuevas", 60 + "no_new_reviews": "No hay repasos nuevos", 61 + "open_source": "Hanzi Offline es de código abierto", 41 62 "other": "Otro", 42 - "override_progress_prompt": "¿Reemplazar con este progreso?", 63 + "override_progress_prompt": "¿Sobreescribir con este progreso?", 43 64 "paired": "Emparejado", 44 65 "passed": "Aprobado", 66 + "play_again": "Jugar de nuevo", 67 + "play_audio": "Reproducir audio", 68 + "practice_type": "Tipo de práctica", 45 69 "progress": "Progreso", 46 70 "radical": "Radical", 47 71 "radicals": "Radicales", 48 72 "random": "Aleatorio", 49 73 "reading": "Lectura", 50 - "reading_first": "Lectura Primero", 51 - "reset_progress": "Restablecer Progreso", 74 + "reading_first": "Lectura primero", 75 + "reset_progress": "Restablecer progreso", 52 76 "reviews": "Repasos", 53 77 "transliteration": "Transliteración", 54 78 "save": "Guardar", 79 + "sentences_completed": "oraciones completadas", 80 + "sentences_game": "Practicar oraciones", 81 + "sentences_none": "No hay oraciones disponibles aún. ¡Sigue estudiando para desbloquear más!", 55 82 "settings": "Ajustes", 83 + "show_answer": "Mostrar respuesta", 84 + "skip_to_words": "Ir a palabras", 56 85 "sound": "Sonido", 86 + "speaking": "Hablar", 87 + "strokes": "Trazos", 88 + "studied": "Estudiado", 57 89 "study": "Estudiar", 58 - "study_group_size": "Tamaño de Grupo de Estudio", 90 + "study_group_size": "Tamaño del grupo de estudio", 59 91 "study_group_size_hint": "El número máximo de elementos a estudiar a la vez.", 60 92 "sync": "Sincronizar", 61 93 "sync_url": "URL de Synclink", 62 94 "synonyms": "Sinónimos", 63 - "card_order": "Orden de Tarjetas", 64 - "card_sort_method": "Método de Ordenación", 65 - "card_sort_hint": "Cómo se ordenan las tarjetas de significado y lectura en una sesión de estudio.", 66 95 "test_connection": "Conectar", 67 96 "tocfl": "TOCFL", 68 - "todays_words": "Palabras de Hoy", 69 - "total_learned": "Total Aprendido", 70 - "use_app_for_audio": "¡Descarga la app móvil para descargar audio para uso sin conexión!", 97 + "todays_words": "Palabras de hoy", 98 + "tones": "Tonos", 99 + "total_learned": "Total aprendido", 100 + "typing": "Teclear", 101 + "use_app_for_audio": "¡Descarga la app móvil para descargar audio sin conexión!", 71 102 "user_language": "Idioma", 72 103 "vocabulary": "Vocabulario", 73 - "zh_CN": "China (Mandarín, Simplificado)", 74 - "zh_HK": "Hong Kong (Cantonés, Tradicional)", 75 - "zh_TW": "Taiwán (Mandarín, Tradicional)" 104 + "writing": "Escritura", 105 + "zh_CN": "China (mandarín, simplificado)", 106 + "zh_HK": "Hong Kong (cantonés, tradicional)", 107 + "zh_TW": "Taiwán (mandarín, tradicional)", 108 + "zhuyin": "Zhuyin", 109 + "zhuyin_game": "Aprender Zhuyin (Bopomofo)", 110 + "zhuyin_instructions_daily": "Selecciona 1-2 nuevos conjuntos de ㄅㄆㄇ para aprender por día (¡de uno en uno!).", 111 + "zhuyin_instructions_words": "También puede ayudar añadir los conjuntos del día anterior. Las palabras de práctica incluyen todos los conjuntos anteriores.", 112 + "zhuyin_repeat_note": "Nota: esta lista se repetirá indefinidamente", 113 + "zhuyin_typing_hint": "escribe pinyin…", 114 + "zhuyin_words_note": "¡Aquí hay algunas palabras para practicar! Concéntrate en los sonidos: no te preocupes por memorizar los significados todavía." 76 115 }
+1 -1
www/static/styles/theme.css
··· 956 956 } 957 957 958 958 .redo { 959 - margin: auto; 959 + margin-left: var(--s2); 960 960 display: block; 961 961 } 962 962