diff --git a/android/app/build.gradle b/android/app/build.gradle index b14e8287..3ba2befa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.esl.ionic" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1003009 - versionName "10.3.9" + versionCode 1003010 + versionName "10.3.10" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/android/fastlane/metadata/android/en-US/changelogs/default.txt b/android/fastlane/metadata/android/en-US/changelogs/default.txt index acc61753..914a1191 100644 --- a/android/fastlane/metadata/android/en-US/changelogs/default.txt +++ b/android/fastlane/metadata/android/en-US/changelogs/default.txt @@ -1 +1 @@ -Each dictation in a list now shows a short preview under the title, so you can pick the right practice without opening it. +Search by title, words, or ID, then press Enter. Age, type, and date are chips you can tap to change, and each list row now shows its preview at the bottom. diff --git a/android/fastlane/metadata/android/zh-CN/changelogs/default.txt b/android/fastlane/metadata/android/zh-CN/changelogs/default.txt index 1520e378..67a39629 100644 --- a/android/fastlane/metadata/android/zh-CN/changelogs/default.txt +++ b/android/fastlane/metadata/android/zh-CN/changelogs/default.txt @@ -1 +1 @@ -列表里每份练习的标题下会显示一行预览,方便你不用打开就能选对听写练习。 +可以用标题、生字或 ID 寻找练习,按回车开始。年龄、类型和日期点一下就能改,列表里每份练习的预览改到最下方。 diff --git a/android/fastlane/metadata/android/zh-TW/changelogs/default.txt b/android/fastlane/metadata/android/zh-TW/changelogs/default.txt index 7989093d..22cba292 100644 --- a/android/fastlane/metadata/android/zh-TW/changelogs/default.txt +++ b/android/fastlane/metadata/android/zh-TW/changelogs/default.txt @@ -1 +1 @@ -列表裡每份練習的標題下會顯示一行預覽,方便你不用打開就能選對默書。 +可以用標題、生字或 ID 尋找練習,按 Enter 開始。年齡、類型和日期點一下就能改,列表裡每份練習的預覽改到最下方。 diff --git a/e2e/search-dictation-open-by-id.spec.ts b/e2e/search-dictation-open-by-id.spec.ts index b4025501..fd38681c 100644 --- a/e2e/search-dictation-open-by-id.spec.ts +++ b/e2e/search-dictation-open-by-id.spec.ts @@ -10,7 +10,7 @@ test.describe('search dictation opens by id', () => { test('view the first search result on /dictation-view/:id', async ({ page }) => { await page.goto('/search-dictation'); - const keyword = page.locator('ion-searchbar input'); + const keyword = page.locator('ion-input[formcontrolname="keyword"] input'); await keyword.click(); await keyword.fill('apple'); diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index c173ae8f..7d6a5446 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -354,12 +354,12 @@ CODE_SIGN_ENTITLEMENTS = App/App.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1003009; + CURRENT_PROJECT_VERSION = 1003010; DEVELOPMENT_TEAM = DU483K8F48; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 18.6; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 10.3.9; + MARKETING_VERSION = 10.3.10; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.esl.ionic; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -378,12 +378,12 @@ CODE_SIGN_ENTITLEMENTS = App/App.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1003009; + CURRENT_PROJECT_VERSION = 1003010; DEVELOPMENT_TEAM = DU483K8F48; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 18.6; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 10.3.9; + MARKETING_VERSION = 10.3.10; PRODUCT_BUNDLE_IDENTIFIER = com.esl.ionic; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index ee92329a..049686b3 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 10.3.9 + 10.3.10 CFBundleURLTypes @@ -34,7 +34,7 @@ CFBundleVersion - 1003009 + 1003010 LSApplicationCategoryType LSRequiresIPhoneOS diff --git a/ios/App/fastlane/metadata/en-US/release_notes.txt b/ios/App/fastlane/metadata/en-US/release_notes.txt index acc61753..914a1191 100644 --- a/ios/App/fastlane/metadata/en-US/release_notes.txt +++ b/ios/App/fastlane/metadata/en-US/release_notes.txt @@ -1 +1 @@ -Each dictation in a list now shows a short preview under the title, so you can pick the right practice without opening it. +Search by title, words, or ID, then press Enter. Age, type, and date are chips you can tap to change, and each list row now shows its preview at the bottom. diff --git a/ios/App/fastlane/metadata/zh-Hans/release_notes.txt b/ios/App/fastlane/metadata/zh-Hans/release_notes.txt index 1520e378..67a39629 100644 --- a/ios/App/fastlane/metadata/zh-Hans/release_notes.txt +++ b/ios/App/fastlane/metadata/zh-Hans/release_notes.txt @@ -1 +1 @@ -列表里每份练习的标题下会显示一行预览,方便你不用打开就能选对听写练习。 +可以用标题、生字或 ID 寻找练习,按回车开始。年龄、类型和日期点一下就能改,列表里每份练习的预览改到最下方。 diff --git a/ios/App/fastlane/metadata/zh-Hant/release_notes.txt b/ios/App/fastlane/metadata/zh-Hant/release_notes.txt index 7989093d..22cba292 100644 --- a/ios/App/fastlane/metadata/zh-Hant/release_notes.txt +++ b/ios/App/fastlane/metadata/zh-Hant/release_notes.txt @@ -1 +1 @@ -列表裡每份練習的標題下會顯示一行預覽,方便你不用打開就能選對默書。 +可以用標題、生字或 ID 尋找練習,按 Enter 開始。年齡、類型和日期點一下就能改,列表裡每份練習的預覽改到最下方。 diff --git a/package-lock.json b/package-lock.json index 9087c9a4..b7363188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "esl-ionic", - "version": "10.3.9", + "version": "10.3.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "esl-ionic", - "version": "10.3.9", + "version": "10.3.10", "dependencies": { "@angular/animations": "^21.1.3", "@angular/common": "^21.1.3", diff --git a/package.json b/package.json index 03edacf9..6093b32f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esl-ionic", - "version": "10.3.9", + "version": "10.3.10", "author": "thcathy", "homepage": "https://www.funfunspell.com", "scripts": { diff --git a/release_notes/en.txt b/release_notes/en.txt index acc61753..914a1191 100644 --- a/release_notes/en.txt +++ b/release_notes/en.txt @@ -1 +1 @@ -Each dictation in a list now shows a short preview under the title, so you can pick the right practice without opening it. +Search by title, words, or ID, then press Enter. Age, type, and date are chips you can tap to change, and each list row now shows its preview at the bottom. diff --git a/release_notes/zh-Hans.txt b/release_notes/zh-Hans.txt index 1520e378..67a39629 100644 --- a/release_notes/zh-Hans.txt +++ b/release_notes/zh-Hans.txt @@ -1 +1 @@ -列表里每份练习的标题下会显示一行预览,方便你不用打开就能选对听写练习。 +可以用标题、生字或 ID 寻找练习,按回车开始。年龄、类型和日期点一下就能改,列表里每份练习的预览改到最下方。 diff --git a/release_notes/zh-Hant.txt b/release_notes/zh-Hant.txt index 7989093d..22cba292 100644 --- a/release_notes/zh-Hant.txt +++ b/release_notes/zh-Hant.txt @@ -1 +1 @@ -列表裡每份練習的標題下會顯示一行預覽,方便你不用打開就能選對默書。 +可以用標題、生字或 ID 尋找練習,按 Enter 開始。年齡、類型和日期點一下就能改,列表裡每份練習的預覽改到最下方。 diff --git a/src/app/components/dictation-list/dictation-list.html b/src/app/components/dictation-list/dictation-list.html index e5b4a2ee..2e4023e3 100644 --- a/src/app/components/dictation-list/dictation-list.html +++ b/src/app/components/dictation-list/dictation-list.html @@ -21,12 +21,6 @@ {{dictation.title}} {{ dictation.createdDate | date: 'yyyy-MM-dd' }} - @let line = previewLine(dictation); - - -
{{ line }}
-
-
{{ dictation | dictationQuestions }} @@ -49,6 +43,12 @@ {{ 'Suitable' | translate }}: {{ 'SuitableStudent.' + dictation.suitableStudent | translate }} } + @let line = previewLine(dictation); + + +
{{ line }}
+
+
{{ 'ViewDictation' | translate }} diff --git a/src/app/components/dictation-list/dictation-list.spec.ts b/src/app/components/dictation-list/dictation-list.spec.ts index 8c45bd59..a61dbf77 100644 --- a/src/app/components/dictation-list/dictation-list.spec.ts +++ b/src/app/components/dictation-list/dictation-list.spec.ts @@ -48,7 +48,7 @@ describe('DictationListComponent', () => { expect(component.navService.openDictationById).not.toHaveBeenCalled(); }); - it('renders one preview line under the heading on every row', () => { + it('renders one preview line at the bottom of every row', () => { const described = TestData.fillInDictation(); described.description = 'Fruit words'; described.suitableStudent = 'Kindergarten'; @@ -67,6 +67,7 @@ describe('DictationListComponent', () => { expect(previews.length).toBe(1); const heading = label.querySelector('.heading'); expect(heading.compareDocumentPosition(previews[0]) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(label.lastElementChild.classList.contains('preview-row')).toBeTrue(); }); expect(labels[0].querySelector('.preview-line').textContent.trim()).toBe('Fruit words'); expect(labels[0].querySelector('.preview-line').getAttribute('title')).toBe('Fruit words'); diff --git a/src/app/pages/search-dictation/search-dictation.page.html b/src/app/pages/search-dictation/search-dictation.page.html index 1a734ea0..6d34788b 100644 --- a/src/app/pages/search-dictation/search-dictation.page.html +++ b/src/app/pages/search-dictation/search-dictation.page.html @@ -10,8 +10,10 @@
- - @if (filteredHistory && showHistory) { + + + + @if (showHistory && filteredHistory.length) { @for (h of filteredHistory; track h) { {{h}} @@ -20,7 +22,7 @@ } @if (keyword.invalid && (keyword.dirty || keyword.touched)) {
- @if (keyword.errors.minlength) { + @if (keyword.errors.pattern) {
{{'CharactersTooShort'|translate:{length: '3'} }}
} @if (keyword.errors.maxlength) { @@ -28,11 +30,9 @@ }
} - @if (moreOptions) { - - - - } + + + @if (creator.invalid && (creator.dirty || creator.touched)) {
@if (creator.errors.minlength) { @@ -43,42 +43,52 @@ }
} - @if (moreOptions) { - - +
+ + + +
+ + + @for (state of suitableStudentOptions; track state) { - {{'SuitableStudent.' + state|translate}} + {{'SuitableStudent.' + state|translate}} } -
-
- } - @if (moreOptions) { - - - {{'Any'|translate}} - {{'Word'|translate}} - {{'Sentence'|translate}} - - - } - @if (moreOptions) { - - - @for (option of dateOptions; track option) { - {{option.option|translate}} +
+ + + + + + @for (option of typeOptions; track option.value) { + {{option.label|translate}} } - - - } - - - {{'More Options'|translate}} - - - -  {{'Search'|translate}} - + + + + + + + @for (option of dateOptions; track option.option) { + {{option.option|translate}} + } + + + +
+ +  {{'Search'|translate}} + +
diff --git a/src/app/pages/search-dictation/search-dictation.page.scss b/src/app/pages/search-dictation/search-dictation.page.scss index 219f1148..67ef8866 100644 --- a/src/app/pages/search-dictation/search-dictation.page.scss +++ b/src/app/pages/search-dictation/search-dictation.page.scss @@ -1,5 +1,9 @@ +ion-input { + --placeholder-color: #a0a0a0; +} + .autocomplete { - width: calc(100% - 48px); + width: 100%; position: absolute; z-index: 999; background: #f1f1f1; @@ -34,3 +38,45 @@ ion-button { .error-text { padding-left: 16px; } + +.filters { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; + margin: 12px 16px; +} + +.filter-chip { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + border: 0; + border-radius: 16px; + margin: 0; + padding: 0 10px; + height: 32px; + background: var(--ion-color-primary); + color: var(--ion-color-primary-contrast, #fff); + font-size: 14px; + white-space: nowrap; + cursor: pointer; +} + +.chip-title { + font-weight: 600; +} + +.chip-value { + font-weight: 400; +} + +ion-popover { + --width: max-content; + --max-width: calc(100vw - 32px); +} + +.form-actions { + display: flex; + justify-content: flex-end; +} diff --git a/src/app/pages/search-dictation/search-dictation.page.spec.ts b/src/app/pages/search-dictation/search-dictation.page.spec.ts index d484f5a6..021f43f1 100644 --- a/src/app/pages/search-dictation/search-dictation.page.spec.ts +++ b/src/app/pages/search-dictation/search-dictation.page.spec.ts @@ -1,5 +1,8 @@ import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {TranslateService} from '@ngx-translate/core'; +import {of} from 'rxjs'; import {SearchDictationPage} from './search-dictation.page'; import {SharedTestModule} from '../../../testing/shared-test.module'; @@ -97,6 +100,7 @@ describe('SearchDictationPage', () => { })); it('showHistory only contain history which is started with input keyword', () => { + spyOn(component.dictationService, 'search'); component.history = ['apple', 'banana', 'await']; component.keyword.setValue('a'); component.filterHistory(null); @@ -104,6 +108,99 @@ describe('SearchDictationPage', () => { expect(component.filteredHistory.length).toBe(2); expect(component.filteredHistory[0]).toBe('apple'); expect(component.filteredHistory[1]).toBe('await'); + expect(component.dictationService.search).not.toHaveBeenCalled(); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('.autocomplete ion-item').length).toBe(2); + }); + + it('uses the short keyword placeholder', () => { + const keyword = fixture.nativeElement.querySelector('ion-input[formcontrolname="keyword"]'); + expect(keyword.label).toBe('Search'); + expect(keyword.placeholder).toBe('Title, words, or ID'); + }); + + it('Enter searches only when the form is valid', fakeAsync(() => { + const search = spyOn(component.dictationService, 'search').and.returnValue(of([])); + const keywordInput = fixture.debugElement.query(By.css('ion-input[formcontrolname="keyword"]')); + + component.keyword.setValue('ab'); + fixture.detectChanges(); + keywordInput.triggerEventHandler('keydown.enter', new KeyboardEvent('keydown', {key: 'Enter'})); + tick(); + expect(search).not.toHaveBeenCalled(); + expect(component.results).toBeUndefined(); + + for (const value of ['abc', '1', '']) { + search.calls.reset(); + component.keyword.setValue(value); + fixture.detectChanges(); + keywordInput.triggerEventHandler('keydown.enter', new KeyboardEvent('keydown', {key: 'Enter'})); + tick(); + expect(search).toHaveBeenCalled(); + } + })); + + it('shows Created By without a More Options toggle', () => { + expect(fixture.nativeElement.textContent).not.toContain('More Options'); + const creator = fixture.nativeElement.querySelector('ion-input[formcontrolname="creator"]'); + const filters = fixture.nativeElement.querySelector('.filters'); + expect(creator).toBeTruthy(); + expect(creator.compareDocumentPosition(filters) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + + it('shows the active option as a chip and chooses it from a popover', () => { + const search = spyOn(component.dictationService, 'search'); + expect(fixture.nativeElement.querySelectorAll('.filter-chip').length).toBe(3); + expect(chipText('suitable')).toBe('Suitable (Age): SuitableStudent.Any'); + expect(chipText('type')).toBe('Type: Any'); + expect(chipText('date')).toBe('Date: Any'); + expect(fixture.nativeElement.querySelector('ion-popover[trigger="suitable-filter"]')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('ion-popover[trigger="type-filter"]')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('ion-popover[trigger="date-filter"]')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('ion-select')).toBeNull(); + expect(fixture.nativeElement.querySelector('.filter-choices')).toBeNull(); + + component.chooseType('Vocab'); + fixture.detectChanges(); + expect(component.type.value).toBe('Vocab'); + expect(chipText('type')).toBe('Type: Word'); + + component.chooseSuitable('JuniorPrimary'); + fixture.detectChanges(); + expect(component.suitableStudent.value).toBe('JuniorPrimary'); + expect(chipText('suitable')).toBe('Suitable (Age): SuitableStudent.JuniorPrimary'); + + component.chooseDate(component.dateOptions[2]); + fixture.detectChanges(); + expect(component.minDate.value).toBe(component.dateOptions[2]); + expect(chipText('date')).toBe('Date: Within 3 Month'); + expect(search).not.toHaveBeenCalled(); + }); + + function chipText(group: string): string { + return fixture.nativeElement.querySelector(`[data-filter="${group}"]`).textContent.replace(/\s+/g, ' ').trim(); + } + + it('shows the 3-character message for a too-short keyword', () => { + const translate = TestBed.inject(TranslateService); + translate.setTranslation('en', { + CharactersTooShort: 'cannot less than {{length}} characters', + }); + translate.use('en'); + + component.keyword.setValue('ab'); + component.keyword.markAsDirty(); + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector('.error-text').textContent) + .toContain('cannot less than 3 characters'); + + component.history = ['apple']; + component.filterHistory(null); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.autocomplete')).toBeNull(); + expect(fixture.nativeElement.querySelector('.error-text').textContent) + .toContain('cannot less than 3 characters'); }); it('do not store duplicate search history', fakeAsync(() => { diff --git a/src/app/pages/search-dictation/search-dictation.page.ts b/src/app/pages/search-dictation/search-dictation.page.ts index 11a9fe1b..7c1c2ac6 100644 --- a/src/app/pages/search-dictation/search-dictation.page.ts +++ b/src/app/pages/search-dictation/search-dictation.page.ts @@ -1,7 +1,8 @@ -import {Component, OnInit} from '@angular/core'; +import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; +import {Subscription} from 'rxjs'; import {UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; -import {Dictation, Dictations, SuitableStudentOptions} from '../../entity/dictation'; import {TranslateService} from '@ngx-translate/core'; +import {Dictation, Dictations, SuitableStudentOptions} from '../../entity/dictation'; import {DictationService} from '../../services/dictation/dictation.service'; import {ValidationUtils} from '../../utils/validation-utils'; import {IonicComponentService} from '../../services/ionic-component.service'; @@ -18,18 +19,26 @@ export interface DateSearchOption { styleUrls: ['./search-dictation.page.scss'], standalone: false }) -export class SearchDictationPage implements OnInit { +export class SearchDictationPage implements OnInit, OnDestroy { SEARCH_HISTORY_KEY = 'SEARCH_HISTORY_KEY'; MAX_HISTORY = 10; inputForm: UntypedFormGroup; - moreOptions = false; results: Dictation[]; suitableStudentOptions = SuitableStudentOptions; + typeOptions = [ + {value: 'Any', label: 'Any'}, + {value: 'Vocab', label: 'Word'}, + {value: 'Article', label: 'Sentence'}, + ]; dateOptions = this.createDateOptions(); history: String[] = []; filteredHistory: String[] = []; showHistory = false; + suitableChipLabel = ''; + typeChipLabel = ''; + dateChipLabel = ''; + private langChange: Subscription; constructor( public formBuilder: UntypedFormBuilder, @@ -37,10 +46,17 @@ export class SearchDictationPage implements OnInit { public translateService: TranslateService, public storage: StorageService, public ionicComponentService: IonicComponentService, + private cdr: ChangeDetectorRef, ) { } ngOnInit() { this.createForm(); + this.refreshChipLabels(); + this.langChange = this.translateService.onLangChange.subscribe(() => this.refreshChipLabels()); + } + + ngOnDestroy() { + this.langChange?.unsubscribe(); } get keyword() { return this.inputForm.get('keyword'); } @@ -67,6 +83,39 @@ export class SearchDictationPage implements OnInit { }); } + chooseSuitable(state: string) { + this.suitableStudent.setValue(state); + this.refreshChipLabels(); + } + + chooseType(value: string) { + this.type.setValue(value); + this.refreshChipLabels(); + } + + chooseDate(option: DateSearchOption) { + this.minDate.setValue(option); + this.refreshChipLabels(); + } + + private refreshChipLabels() { + this.suitableChipLabel = this.translateService.instant('SuitableStudent.' + this.suitableStudent.value); + const typeKey = this.typeOptions.find(option => option.value === this.type.value)?.label ?? ''; + this.typeChipLabel = typeKey ? this.translateService.instant(typeKey) : ''; + const dateKey = this.minDate.value?.option ?? ''; + this.dateChipLabel = dateKey ? this.translateService.instant(dateKey) : ''; + // The dev-mode double check skips this view, then fails when it re-reads the new label. + this.cdr.markForCheck(); + } + + searchOnEnter(event: Event) { + event.preventDefault(); + if (this.inputForm.invalid) { + return; + } + void this.search(); + } + async search() { this.results = null; const loader = await this.ionicComponentService.showLoading(); diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 8fc22d62..101bd05d 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -97,5 +97,6 @@ "Preload.Tip.CreateDictation": "Create your own dictation from any word list.", "Preload.Tip.OfflineMode": "Local voice works offline — great for practice on the go.", "Preload.Tip.Images": "AI images are generated only when a dictation is saved.", - "VocabImage.UnverifiedAiNote": "AI image — may not match this word" + "VocabImage.UnverifiedAiNote": "AI image — may not match this word", + "Title, words, or ID": "Title, words, or ID" } diff --git a/src/assets/i18n/zh-Hans.json b/src/assets/i18n/zh-Hans.json index 1fecdc91..cc256d0e 100644 --- a/src/assets/i18n/zh-Hans.json +++ b/src/assets/i18n/zh-Hans.json @@ -163,7 +163,7 @@ "Dictation not found": "找不到听写练习", "Detail": "详情", "You can use the ID number to search this dictation": "您可以使用 ID 号码来搜索此练习", - "ID or part of title / description": "ID 号码或部分标题/描述", + "Title, words, or ID": "标题、生字或 ID", "Personal Information": "个人资料", "Welcome back": "欢迎回来", "Info": "资讯", diff --git a/src/assets/i18n/zh-Hant.json b/src/assets/i18n/zh-Hant.json index fb03c988..12db5e53 100644 --- a/src/assets/i18n/zh-Hant.json +++ b/src/assets/i18n/zh-Hant.json @@ -163,7 +163,7 @@ "Dictation not found": "找不到練習", "Detail": "詳情", "You can use the ID number to search this dictation": "您可以使用 ID 號碼來搜索此練習", - "ID or part of title / description": "ID 號碼或部分標題/描述", + "Title, words, or ID": "標題、生字或 ID", "Personal Information": "個人資料", "Welcome back": "歡迎回來", "Info": "資訊",