diff --git a/gcompris.appdata.xml b/gcompris.appdata.xml index 1d939b684..08ce46a62 100644 --- a/gcompris.appdata.xml +++ b/gcompris.appdata.xml @@ -1,712 +1,720 @@ gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop gcompris.desktop xxgcompris.desktopxx gcompris.desktop gcompris.desktop CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 CC0-1.0 xxCC0-1.0xx CC0-1.0 CC0-1.0 GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ GPL-3.0+ xxGPL-3.0+xx GPL-3.0+ GPL-3.0+ GCompris Educational Game Joc educatiu GCompris GCompris-Lernspiel GCompris Educational Game Juego educativo GCompris GCompris, opettavainen peli Jeu éducatif GCompris Xogo educativo GCompris Gioco didattico GCompris GCompris 교육용 게임 Educatief spel GCompris GCompris leik-og-lær-spel Gra edukacyjna GCompris Jogo Educativo GCompris Jogo educacional GCompris Joc educațional GCompris Výuková hra GCompris Izobraževalna igra GCompris GCompris pedagogiskt spel Освітня гра GCompris xxGCompris Educational Gamexx GCompris 教育游戏 GCompris 教育遊戲 Multi-Activity Educational game for children 2 to 10 Joc educatiu amb múltiples activitats per a nens dels 2 a 10 anys Lernspiel mit vielen Aktivitäten für Kinder von 2 bis 10 Jahren Multi-Activity Educational game for children 2 to 10 Juego educativo multiactividad para niños de 2 a 10 años Useampitoimintoinen opettavainen peli 2–10-vuotiaille lapsille Jeu éducatif multi-activités pour les enfants de 2 à 10 ans Xogo educativo con varias actividades para nenos de entre 2 e 10 anos. Gioco didattico multi-attività per bambini da 2 a 10 anni 2-10세 어린이를 위한 다양한 활동이 있는 교육용 게임 Educatief spel met meerdere activiteiten voor kinderen van 2 tot 10 Leik-og-lærspel med mange aktivitetar – for barn frå 2 til 10 år Gra edukacyjna z wieloma aktywnościami dla dzieci w wieku od 2 do 10 lat Jogo Educativo Multi-Actividades para crianças dos 2 aos 10 anos Jogo educacional com várias atividades para crianças de 2 a 10 anos Joc educațional cu activități multiple pentru copii între 2 și 10 ani Viac-aktivitová výuková hra pre deti od 2 do 10 rokov Izobraževalna igra z več dejavnostmi za otroke med drugim in desetim letom starosti Pedagogiskt multiaktivitetsspel för barn från 2 till 10 år Набір освітніх ігор для дітей від 2 до 10 років xxMulti-Activity Educational game for children 2 to 10xx 为 2 到 10 岁儿童准备的多功能教育游戏 為 2-10 歲孩子設計的教育遊戲

GCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.

El GCompris és una suite de programari educatiu que consisteix de nombroses activitats per a nens i nenes entre els 2 i els 10 anys.

GCompris ist eine Lernsoftware, die verschiedene Aktivitäten für Kinder im Alter von 2 bis 10 Jahren anbietet.

GCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.

GCompris es una suite de software educativo que consta de numerosas actividades para niños de 2 a 10 años.

GCompris on useista toiminnoista koostuva opetusohjelmisto 2–10-vuotiaille lapsille.

GCompris est une suite éducative comprenant de nombreuses activités pour les enfants de 2 à 10 ans.

GCompris é unha colección de programas educativos composta por numerosas actividades para nenos de entre 2 e 10 anos.

GCompris è una raccolta di programmi didattici che comprende numerose attività per bambini da 2 a 10 anni.

GCompris는 2-10세 어린이를 위한 활동을 모아 놓은 교육용 소프트웨어 모음입니다.

GCompris is suite met educatieve software die bestaat uit vele activiteiten voor kinderen vanaf twee tot tien jaar.

GCompris er eit leik-og-lær spel med mange ulike aktivitetar, laga for ungar frå 2 til 10 år.

GCompris to pakiet oprogramowania edukacyjnego z licznymi aktywnościami dla dzieci w wieku od 2 do 10 lat.

O GCompris é um pacote de aplicações educativas que é composto por diversas actividades para as crianças dos 2 aos 10 anos de idade.

GCompris é uma suíte de software educacional que apresenta uma série de atividades para crianças com idade entre 2 e 10 anos.

GCompris este un pachet de software educațional compus din numeroase activități pentru copii între 2 și 10 ani.

GCompris je balík výukového softvéru zložený z rôznych aktivít pre deti od 2 do 10 rokov.

Program GCompris je visokokakovostna zbirka izobraževalnih dejavnosti, namenjenih otrokom med drugim in desetim letom starosti.

GCompris är en pedagogisk programsvit som består av en mängd olika aktiviteter för barn från 2 till 10 år gamla.

GCompris — комплекс навчального програмного забезпечення, що складається з багатьох вправ для дітей від 2 до 10 років.

xxGCompris is an educational software suite comprising of numerous activities for children aged 2 to 10.xx

GCompris 是一款教育套件,由针对 2 到 10 岁孩子的许多活动组成。

GCompris 是一套教育軟體的集合,它提供適合兩歲到十歲兒童各種不同的活動。

Some of the activities are game orientated, but nonetheless still educational.

Algunes de les activitats estan orientades a joc, però sempre són educatives.

Některé aktivity jsou v podobě her, ale přesto jsou poučné.

Einige Aktivitäten sind eher spielerisch, aber immer lehrreich.

Some of the activities are game orientated, but nonetheless still educational.

Algunas de las actividades son juegos, aunque siguen siendo educativas.

Jotkin tehtävistä ovat pelimäisiä mutta silti opettavaisia.

Certaines activités sont ludiques mais ont toujours un intérêt pédagogique.

Algunhas das actividades están orientadas ao xogo, pero son aínda así educativas.

Alcune delle attività sono orientate al gioco, ma comunque didattiche.

일부 활동은 기능성 게임을 지향합니다.

Sommige activiteiten zijn spelletjes, maar toch leerzaam.

Nokre av aktivitetane er mest som spel, men er likevel lærerike.

Niektóre z aktywności są bardziej grą, lecz nadal bardzo pouczającą.

Algumas das actividades são apresentadas como jogos, mas são educativas à mesma.

Algumas das atividades são de orientação lúdica, mas mesmo assim ainda educacional.

Unele activități sunt orientate pe joacă, dar totuși educative.

Niektoré z aktivít sú herne orientované, ale stále výukové.

Nekatere dejavnosti so bolj podobne igram, vendar še vedno poučne.

Vissa av aktiviteterna är spelorienterade, men trots det ändå pedagogiska.

Деякі із вправ є суто ігровими, але містять і елементи навчання.

xxSome of the activities are game orientated, but nonetheless still educational.xx

一些活动是游戏导向的,不过尽管如此仍是教育性的。

有些活動是遊戲性質,但還是有很棒的教育意義。

Below you can find a list of categories with some of the activities available in that category.

A continuació trobareu una llista de les categories amb algunes de les activitats disponibles en cadascuna.

Sie können Aktivitäten aus folgenden Bereichen in GCompris finden:

Below you can find a list of categories with some of the activities available in that category.

A continuación puede encontrar una lista de categorías con algunas de las actividades disponibles en cada una de ellas.

Alta löydät luettelot luokista sekä maininnan joistakin kuhunkin luokkaan kuuluvista toiminnoista.

Vous pouvez trouver ci-dessous une liste des catégories avec certaines de leurs activités.

A continuación atopará unha lista de categorías e mailas actividades dispoñíbeis en cada unha desas categorías.

Di seguito puoi trovare un elenco di categorie con alcune delle attività disponibili per categoria.

아래에서 분류 목록과 해당 분류의 활동을 찾을 수 있습니다.

Hieronder vindt u een lijst met categorieën met enige van de activiteiten beschikbaar in die categorie.

Nedanfor finn du ei oversikt over kategoriar, saman med nokre av aktivitetane i kvar kategori.

Poniżej znajdziesz wykaz kategorii z niektórymi aktywnościami dostępnymi w tej kategorii.

Em baixo, poderá encontrar uma lista de categorias, com algumas das actividades disponíveis para essa categoria.

Abaixo está relacionada uma lista de categorias com algumas das atividades disponíveis em cada uma delas.

Dedesubt puteți găsi o listă de categorii cu unele din activitățile disponibile în acea categorie.

Tu môžete nájsť zoznam kategórií s niektorými aktivitami dostupnými v kategórii.

Spodaj lahko najdete seznam kategorij in nekaj dejavnosti, ki so na voljo v tisti kategoriji.

Nedan finns en lista över kategorier med några av aktiviteterna tillgängliga i varje kategori.

Нижче наведено список категорій із деякими вправами у цих категоріях.

xxBelow you can find a list of categories with some of the activities available in that category.xx

+

下面列出了一些分类和对应分类中的活动。

下方您可以看到該類別的活動清單。

Currently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.

Actualment el GCompris ofereix més de 100 activitats i encara se'n desenvolupen més. El GCompris és programari lliure, el qual vol dir que podeu adaptar-lo a les vostres pròpies necessitats, millorar-lo i, el més important, compartir-lo amb nens de tot arreu.

Insgesamt beinhaltet GCompris mehr als 100 Aktivitäten und wird ständig weiterentwickelt. GCompris ist freie Software. Sie haben also die Möglichkeit, sie ihren Bedürfnissen anzupassen und zu verbessern, und gerne auch alle Kinder der Welt von Ihrer Arbeit profitieren zu lassen.

Currently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.

En la actualidad, GCompris ofrece más de 100 actividades y hay muchas más en desarrollo. GCompris es software libre, lo que significa que puede adaptarlo a sus necesidades, mejorarlo y, lo que es más importante, compartirlo con los niños de todo el mundo.

GComprisissa on nykyisellään yli sata toimintoa, ja lisää kehitetään. GCompris on vapaata ohjelmistoa, mikä tarkoittaa, että voit muokata sitä tarpeisiisi, parannella sitä ja mikä tärkeintä jakaa sitä lapsille kaikkialla.

Actuellement, GCompris contient plus de 100 activités et d'autres sont en cours de développement. GCompris est un logiciel libre, ce qui veut dire que vous pouvez l'adapter à vos propres besoins, le modifier et, le plus important, le partager avec des enfants où que ce soit.

Actualmente GCompris ofrece máis de 100 actividades e hai máis en progreso. GCompris é software libre, o que significa que pode adaptalo para axustalo ás súas necesidades, melloralo e, o máis importante, compartilo con nenos de todo o mundo.

Attualmente GCompris offre più di 100 attività e altre sono in fase di sviluppo. GCompris è software libero, che significa che puoi adattarlo alle tue necessità, migliorarlo e, cosa più importante, condividerlo con tutti i bambini.

GCompris는 100개 이상의 활동을 포함하고 있으며 더 많은 활동을 개발하고 있습니다. GCompris는 자유 소프트웨어이며, 필요에 따라서 활동을 개선할 수 있으며 전 세계의 사람들과 공유할 수 있습니다.

Op dit moment biedt GCompris meer dan 100 activiteiten en meer zijn er in ontwikkeling. GCompris is vrije software, dat betekent dat u het kunt aanpassen aan uw eigen behoeften, het verbeteren en, meest belangrijk, het met kinderen overal kun delen.

GCompris har over 100 aktivitetar, og fleire vert utvikla. GCompris er fri programvare. Det vil seia at du kan tilpassa spelet slik du ønskjer, forbetra det og dela det med andre ungar i heile verda.

Obecnie GCompris zapewnia ponad 100 aktywności, a jeszcze więcej jest opracowywanych. GCompris jest darmowym oprogramowaniem, co oznacza, że możesz je dopasować do własnych potrzeb, ulepszać je i co najważniejsze udostępniać je dzieciom.

De momento, o GCompris oferece mais de 100 actividades, estando ainda mais em desenvolvimento. O GCompris é uma aplicação de 'software' livre, o que significa que o poderá adaptar às suas próprias necessidades, melhorá-lo e, o mais importante, partilhá-lo com as crianças em todo o lado.

Atualmente o GCompris oferece mais de 100 atividades e outras mais estão sendo desenvolvidas. O GCompris é um software livre, o que significa que você pode adaptá-lo para as suas próprias necessidades, melhorá-lo e, mais importante, compartilhar com as crianças de todo o mundo.

Momentan GCompris oferă peste 100 de activități și multe altele sunt în lucru. GCompris este software liber, ceea ce înseamnă că poate fi adaptat nevoilor dumneavoastră, îmbunătățit, dar mai ales, partajat cu copii de pretutindeni.

Aktuálne GCompris ponúka vyše 100 aktivít a ďalšie sa vyvíjajú. GCompris je slobodný softvér, čo znamená, že si ho môžete prispôsobiť na vlastné potreby, zlepšiť, a hlavne zdieľať s deťmi všade.

GCompris trenutno ponuja več kot 100 dejavnosti, še več pa jih je v razvoju. GCompris je prosta programska oprema, kar pomeni, da jo lahko prilagodite po svoje, še pomembneje pa je, da jo lahko delite z otroki širom sveta.

För närvarande erbjuder GCompris över 100 aktiviteter och ännu fler håller på att utvecklas. GCompris är fri programvara, vilket betyder att man kan anpassa det till sina egna behov, och viktigast av allt, dela det med barn överallt.

У поточній версії GCompris понад 100 вправ. Розробники постійно працюють над новими вправами. GCompris є вільним програмним забезпеченням, це означає, що ви можете адаптувати програму до власних потреб, покращувати її і, що найважливіше, ділитися вашими творіннями із усіма дітьми.

xxCurrently GCompris offers in excess of 100 activities and more are being developed. GCompris is free software, that means that you can adapt it to your own needs, improve it and, most importantly, share it with children everywhere.xx

+

当前 GCompris 提供超过100个活动,还有更多的正在开发中。GCompris 是自由软件,这意味着你可以根据你的需要进行调整、改进,以及最重要的一点,和世界各地的孩子们分享。

目前 GCompris 提供超過 100 個活動,還有更多正在開發中。GCompris 是一套自由軟體,表示您可以根據您自己的需求來改進,並分享給所有的孩子們。

http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png xxhttp://gcompris.net/screenshots-qt/middle/canal_lock.pngxx http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/canal_lock.png http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ xxhttp://gcompris.net/screenshots-qt/middle/xx http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/ http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png xxhttp://gcompris.net/screenshots-qt/middle/click_on_letter_up.pngxx http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/click_on_letter_up.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png xxhttp://gcompris.net/screenshots-qt/middle/clockgame.pngxx http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/clockgame.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png xxhttp://gcompris.net/screenshots-qt/middle/color_mix.pngxx http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/color_mix.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png xxhttp://gcompris.net/screenshots-qt/middle/colors.pngxx http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/colors.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png xxhttp://gcompris.net/screenshots-qt/middle/enumerate.pngxx http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/enumerate.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png xxhttp://gcompris.net/screenshots-qt/middle/fifteen.pngxx http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/fifteen.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png xxhttp://gcompris.net/screenshots-qt/middle/hexagon.pngxx http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/hexagon.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png xxhttp://gcompris.net/screenshots-qt/middle/redraw_symmetrical.pngxx http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/redraw_symmetrical.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png xxhttp://gcompris.net/screenshots-qt/middle/scalesboard.pngxx http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/scalesboard.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png xxhttp://gcompris.net/screenshots-qt/middle/traffic.pngxx http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/screenshots-qt/middle/traffic.png http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ http://gcompris.net/ xxhttp://gcompris.net/xx http://gcompris.net/ http://gcompris.net/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris xxhttps://bugs.kde.org/enter_bug.cgi?format=guided&product=gcomprisxx https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris https://bugs.kde.org/enter_bug.cgi?format=guided&product=gcompris bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net xxbruno.coudoin@gcompris.netxx bruno.coudoin@gcompris.net bruno.coudoin@gcompris.net
diff --git a/src/activities/activities.txt b/src/activities/activities.txt index 71a5b9c1b..5686b54a8 100644 --- a/src/activities/activities.txt +++ b/src/activities/activities.txt @@ -1,112 +1,113 @@ # The list of activities that will be loaded at GCompris start. # Keep it sorted advanced_colors algebra_by algebra_div algebra_minus algebra_plus algorithm align4 align4-2players alphabet-sequence babymatch babyshapes ballcatch braille_alphabets braille_fun canal_lock chess chess_2players chess_partyend chronos clickanddraw clickgame click_on_letter click_on_letter_up clockgame color_mix color_mix_light colors details drawnumber enumerate erase erase_2clic erase_clic fifteen followline football geo-country geography gletters gnumch-equality gnumch-factors gnumch-inequality gnumch-multiples gnumch-primes guessnumber hanoi hanoi_real hexagon -imageid imagename instruments intro_gravity +lang leftright lightsoff louis-braille magic-hat-minus magic-hat-plus maze mazeinvisible mazerelative +melody memory memory-enumerate memory-math-add memory-math-add-minus memory-math-add-minus-mult-div memory-math-add-minus-mult-div-tux memory-math-add-minus-tux memory-math-add-tux memory-math-div memory-math-div-tux memory-math-minus memory-math-minus-tux memory-math-mult memory-math-mult-div memory-math-mult-div-tux memory-math-mult-tux memory-sound memory-sound-tux memory-tux memory-wordnumber mining missing-letter money money_back money_back_cents money_cents mosaic numbers-odd-even penalty planegame readingh readingv redraw redraw_symmetrical renewable_energy reversecount scalesboard scalesboard_weight scalesboard_weight_avoirdupois simplepaint smallnumbers smallnumbers2 sudoku superbrain target tic_tac_toe tic_tac_toe_2players traffic watercycle wordsgame diff --git a/src/activities/align4-2players/Align42players.qml b/src/activities/align4-2players/Align42players.qml index bd2ebb411..0ba12df4a 100644 --- a/src/activities/align4-2players/Align42players.qml +++ b/src/activities/align4-2players/Align42players.qml @@ -1,232 +1,231 @@ /* GCompris - align4-2players.qml * * Copyright (C) 2014 Bharath M S * * Authors: * Laurent Lacheny (GTK+ version) * Bharath M S (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import "../../core" import "align4.js" as Activity import GCompris 1.0 ActivityBase { id: activity property bool twoPlayer: true onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias fallingPiece: fallingPiece property alias pieces: pieces property alias dynamic: dynamic property alias drop: drop property alias player1_score: player1_score.text property alias player2_score: player2_score.text property alias bar: bar property alias bonus: bonus property alias repeater: repeater property alias columns: grid.columns property alias rows: grid.rows property int cellSize: Math.min(background.width / (columns + 1), background.height / (rows + 3)) property bool gameDone property int counter } onStart: { Activity.start(items, twoPlayer) } onStop: { Activity.stop() } Keys.onRightPressed: Activity.moveCurrentIndexRight(); Keys.onLeftPressed: Activity.moveCurrentIndexLeft(); Keys.onDownPressed: Activity.handleDrop(Activity.currentLocation); Keys.onSpacePressed: Activity.handleDrop(Activity.currentLocation); ListModel { id: pieces } Grid { id: grid anchors.horizontalCenter: parent.horizontalCenter anchors { top: parent.top topMargin: items.cellSize + 5 * ApplicationInfo.ratio horizontalCenter: parent.horizontalCenter } spacing: 5 columns: 7 rows: 6 Repeater { id: repeater model: pieces delegate: blueSquare Component { id: blueSquare Rectangle { color: "#DDAAAAAA"; width: items.cellSize height: items.cellSize border.color: "#FFFFFFFF" border.width: 0 Piece { anchors.fill: parent state: stateTemp } } } } Piece { id: fallingPiece state: items.gameDone ? "invisible" : items.counter % 2 ? "2": "1" - width: items.cellSize - height: items.cellSize + sourceSize.width: items.cellSize Behavior on x { PropertyAnimation { duration: 200 } } } } PropertyAnimation { id: drop target: fallingPiece properties: "y" duration: 720 onStarted: activity.audioEffects.play(Activity.url + 'slide.wav') onStopped: { dynamic.display() Activity.continueGame() } } MouseArea { id: dynamic anchors.fill: parent enabled: !drop.running && !items.gameDone hoverEnabled: !drop.running && !items.gameDone property bool holdMode: true function display() { var coord = grid.mapFromItem(background, mouseX, mouseY) Activity.setPieceLocation(coord.x, coord.y) } onPositionChanged: items.dynamic.enabled ? display() : '' onPressed: holdMode = false onPressAndHold: holdMode = true onClicked: { display() if(!holdMode) { var coord = grid.mapFromItem(background, mouseX, mouseY) var column = Activity.whichColumn(coord.x, coord.y) Activity.handleDrop(column) } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | reload | (twoPlayer ? 0 : level) } onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: activity.home() onReloadClicked: { Activity.reset() } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() } Image { id: player1 source: Activity.url + "score_1.svg" sourceSize.height: bar.height * 1.1 anchors { bottom: parent.width > parent.height ? bar.bottom : bar.top bottomMargin: 10 right: parent.right rightMargin: 2 * ApplicationInfo.ratio } GCText { id: player1_score anchors.verticalCenter: parent.verticalCenter x: parent.width / 2 + 5 color: "white" fontSize: largeSize } } Image { id: player2 source: Activity.url + "score_2.svg" sourceSize.height: bar.height * 1.1 anchors { bottom: parent.width > parent.height ? bar.bottom : bar.top bottomMargin: 10 right: player1.left rightMargin: 2 * ApplicationInfo.ratio } GCText { id: player2_score anchors.verticalCenter: parent.verticalCenter color: "white" x: parent.width / 2 + 5 fontSize: largeSize } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/align4-2players/Piece.qml b/src/activities/align4-2players/Piece.qml index 575bb6b97..4251f2e07 100644 --- a/src/activities/align4-2players/Piece.qml +++ b/src/activities/align4-2players/Piece.qml @@ -1,73 +1,74 @@ /* GCompris - align4-2players.qml * * Copyright (C) 2014 Bharath M S * * Authors: * Laurent Lacheny (GTK+ version) * Bharath M S (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.0 import "align4.js" as Activity import GCompris 1.0 Image { id: piece + fillMode: Image.PreserveAspectFit opacity: 1.0 states: [ State { name: "invisible" PropertyChanges { target: piece opacity: 0 } }, State { name: "2" // Player 2 PropertyChanges{ target: piece opacity: 1.0 source: Activity.url + "stone_2.svg" } }, State { name: "1" // Player 1 PropertyChanges { target: piece opacity: 1.0 source: Activity.url + "stone_1.svg" } }, State { name: "crossed1" PropertyChanges { target: piece opacity: 1.0 source: Activity.url + "win1.svg" } }, State { name: "crossed2" PropertyChanges { target: piece opacity: 1.0 source: Activity.url + "win2.svg" } } ] } diff --git a/src/activities/imageid/ActivityInfo.qml b/src/activities/imageid/ActivityInfo.qml deleted file mode 100644 index e2c88c99b..000000000 --- a/src/activities/imageid/ActivityInfo.qml +++ /dev/null @@ -1,35 +0,0 @@ -/* GCompris - ActivityInfo.qml - * - * Copyright (C) 2015 Holger Kaelberer - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ -import GCompris 1.0 - -ActivityInfo { - name: "imageid/Imageid.qml" - difficulty: 2 - icon: "imageid/imageid.svg" - author: "Holger Kaelberer <holger.k@elberer.de>" - demo: true - title: qsTr("Reading practice") - description: qsTr("Practice reading by finding the word matching an image") -// intro: "Click on the word matching the picture." - goal: qsTr("Letter association between the screen and the keyboard") - prerequisite: qsTr("Reading") - manual: qsTr("Click on the word corresponding to the printed image.") - credit: "" - section: "reading" - enabled: DownloadManager.isDataRegistered("words") || ApplicationInfo.isDownloadAllowed -} diff --git a/src/activities/imageid/CMakeLists.txt b/src/activities/imageid/CMakeLists.txt deleted file mode 100644 index 649e35cb2..000000000 --- a/src/activities/imageid/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -GCOMPRIS_ADD_RCC(activities/imageid *.qml *.svg *.js resource/*) diff --git a/src/activities/imageid/Imageid.qml b/src/activities/imageid/Imageid.qml deleted file mode 100644 index 3961e7cbd..000000000 --- a/src/activities/imageid/Imageid.qml +++ /dev/null @@ -1,350 +0,0 @@ -/* GCompris - Imageid.qml - * - * Copyright (C) 2014 Holger Kaelberer - * - * Authors: - * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) - * Holger Kaelberer (Qt Quick port) - * Bruno Coudoin (Integration Lang dataset) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -import QtQuick 2.1 -import GCompris 1.0 -import QtGraphicalEffects 1.0 - -import "../../core" -import "imageid.js" as Activity -import "qrc:/gcompris/src/core/core.js" as Core - -ActivityBase { - id: activity - - onStart: focus = true - onStop: {} - - pageComponent: Image { - id: background - source: "qrc:/gcompris/src/activities/imageid/resource/imageid-bg.svg" - fillMode: Image.PreserveAspectCrop - sourceSize.width: parent.width - - property bool horizontalLayout: background.width > background.height - property bool keyNavigation: false - readonly property string wordsResource: "data2/words/words.rcc" - property bool englishFallback: false - property bool downloadWordsNeeded: false - - signal start - signal stop - signal voiceError - signal voiceDone - - Component.onCompleted: { - activity.start.connect(start) - activity.stop.connect(stop) - } - - QtObject { - id: items - property Item main: activity.main - property alias background: background - property alias bar: bar - property alias bonus: bonus - property alias score: score - property alias wordImage: wordImage - property alias wordListModel: wordListModel - property alias parser: parser - property variant goodWord - property int goodWordIndex - property alias englishFallbackDialog: englishFallbackDialog - - function playWord() { - if (!activity.audioVoices.append(ApplicationInfo.getAudioFilePath(goodWord.voice))) - voiceError(); - } - onGoodWordChanged: playWord() - } - - function handleResourceRegistered(resource) - { - if (resource == wordsResource) - Activity.start(items); - } - - onStart: { - Activity.init(items) - - repeatItem.visible = false - keyNavigation = false - activity.audioVoices.error.connect(voiceError) - activity.audioVoices.done.connect(voiceDone) - - // check for words.rcc: - if (DownloadManager.isDataRegistered("words")) { - // words.rcc is already registered -> start right away - Activity.start(items); - } else if(DownloadManager.haveLocalResource(wordsResource)) { - // words.rcc is there, but not yet registered -> updateResource - DownloadManager.resourceRegistered.connect(handleResourceRegistered); - DownloadManager.updateResource(wordsResource); - } else { - // words.rcc has not been downloaded yet -> ask for download - downloadWordsNeeded = true - } - } - - onStop: { - DownloadManager.resourceRegistered.disconnect(handleResourceRegistered); - Activity.stop() - } - - Keys.onRightPressed: { - keyNavigation = true - wordListView.incrementCurrentIndex() - } - Keys.onLeftPressed: { - keyNavigation = true - wordListView.decrementCurrentIndex() - } - Keys.onDownPressed: { - keyNavigation = true - wordListView.incrementCurrentIndex() - } - Keys.onUpPressed: { - keyNavigation = true - wordListView.decrementCurrentIndex() - } - Keys.onSpacePressed: { - keyNavigation = true - wordListView.currentItem.pressed() - } - Keys.onEnterPressed: { - keyNavigation = true - wordListView.currentItem.pressed() - } - Keys.onReturnPressed: { - keyNavigation = true - wordListView.currentItem.pressed() - } - - JsonParser { - id: parser - - onError: console.error("Imageid: Error parsing json: " + msg); - } - - ListModel { - id: wordListModel - } - - Grid { - id: gridId - columns: horizontalLayout ? 2 : 1 - spacing: 10 * ApplicationInfo.ratio - anchors.fill: parent - anchors.margins: 10 * ApplicationInfo.ratio - - Item { - width: background.horizontalLayout - ? background.width * 0.55 - : background.width - gridId.anchors.margins * 2 - height: background.horizontalLayout - ? background.height - bar.height - : (background.height - bar.height) * 0.4 - Image { - id: imageFrame - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - source: "qrc:/gcompris/src/activities/imageid/resource/imageid_frame.svg" - sourceSize.width: background.horizontalLayout ? parent.width * 0.9 : parent.height * 1.2 - z: 11 - - Image { - id: wordImage - sourceSize.width: parent.width * 0.6 - - anchors { - centerIn: parent - margins: 0.05 + parent.width - } - property string nextSource - function changeSource(nextSource_) { - nextSource = nextSource_ - animImage.start() - } - - SequentialAnimation { - id: animImage - PropertyAnimation { - target: wordImage - property: "opacity" - to: 0 - duration: 100 - } - PropertyAction { - target: wordImage - property: "source" - value: wordImage.nextSource - } - PropertyAnimation { - target: wordImage - property: "opacity" - to: 1 - duration: 100 - } - } - } - } - } - ListView { - id: wordListView - - width: background.horizontalLayout - ? background.width * 0.40 - : background.width - gridId.anchors.margins * 2 - height: background.horizontalLayout - ? background.height - bar.height - : (background.height - bar.height) * 0.40 - spacing: 10 * ApplicationInfo.ratio - orientation: Qt.Vertical - verticalLayoutDirection: ListView.TopToBottom - interactive: false - model: wordListModel - - highlight: Rectangle { - width: wordListView.width - height: wordListView.buttonHeight - color: "lightsteelblue" - radius: 5 - visible: background.keyNavigation - y: wordListView.currentItem.y - Behavior on y { - SpringAnimation { - spring: 3 - damping: 0.2 - } - } - } - highlightFollowsCurrentItem: false - focus: true - keyNavigationWraps: true - - property int buttonHeight: height / wordListModel.count * 0.9 - - delegate: AnswerButton { - id: wordRectangle - - width: wordListView.width - height: wordListView.buttonHeight - - textLabel: word - isCorrectAnswer: word === items.goodWord.translatedTxt - onIncorrectlyPressed: Activity.badWordSelected(items.goodWordIndex); - onCorrectlyPressed: Activity.nextSubLevel(); - } - } - } - - onVoiceDone: repeatItem.visible = true - onVoiceError: repeatItem.visible = false - - BarButton { - id: repeatItem - source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; - sourceSize.width: 80 * ApplicationInfo.ratio - - z: 12 - anchors { - top: parent.top - left: parent.left - margins: 10 * ApplicationInfo.ratio - } - onClicked: items.playWord() - } - - DialogHelp { - id: dialogHelp - onClose: home() - } - - Bar { - id: bar - - content: BarEnumContent { value: help | home | level } - onHelpClicked: { - displayDialog(dialogHelp) - } - onPreviousLevelClicked: Activity.previousLevel() - onNextLevelClicked: Activity.nextLevel() - onHomeClicked: activity.home() - } - - Bonus { - id: bonus - onWin: Activity.nextLevel() - } - - Loader { - id: englishFallbackDialog - sourceComponent: GCDialog { - parent: activity.main - message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + - qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("http://l10n.kde.org/") + - "

" + - qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") - onClose: background.englishFallback = false - } - anchors.fill: parent - focus: true - active: background.englishFallback - onStatusChanged: if (status == Loader.Ready) item.start() - } - - Loader { - id: downloadWordsDialog - sourceComponent: GCDialog { - parent: activity.main - message: qsTr("The images for this activity are not yet installed.") - button1Text: ApplicationInfo.isDownloadAllowed ? qsTr("Download the images") : qsTr("OK") - onClose: background.downloadWordsNeeded = false - onButton1Hit: { - if(ApplicationInfo.isDownloadAllowed) { - DownloadManager.resourceRegistered.connect(handleResourceRegistered); - DownloadManager.downloadResource(wordsResource) - Core.showDownloadDialog(activity, {}); - } - } - } - anchors.fill: parent - focus: true - active: background.downloadWordsNeeded - onStatusChanged: if (status == Loader.Ready) item.start() - } - - Score { - id: score - - anchors.bottom: parent.bottom - anchors.bottomMargin: 10 * ApplicationInfo.ratio - anchors.right: parent.right - anchors.rightMargin: 10 * ApplicationInfo.ratio - anchors.top: undefined - } - - } -} diff --git a/src/activities/imageid/imageid.js b/src/activities/imageid/imageid.js deleted file mode 100755 index 69b8ce449..000000000 --- a/src/activities/imageid/imageid.js +++ /dev/null @@ -1,145 +0,0 @@ -/* GCompris - imageid.js - * - * Copyright (C) 2014 Holger Kaelberer - * - * Authors: - * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) - * Holger Kaelberer (Qt Quick port) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -.pragma library -.import QtQuick 2.0 as Quick -.import GCompris 1.0 as GCompris -.import "qrc:/gcompris/src/core/core.js" as Core -.import "qrc:/gcompris/src/activities/imageid/lang_api.js" as Lang - -var currentLevel = 0; -var currentSubLevel = 0; -var level = null; -var maxLevel; -var maxSubLevel; -var items; -var baseUrl = "qrc:/gcompris/src/activities/imageid/resource/"; -var dataset = null; -var lessons -var wordList -var subLevelsLeft - -function init(items_) { - items = items_; - maxLevel = 0 - maxSubLevel = 0 - currentLevel = 0 - currentSubLevel = 0 -} - -function start() { - currentLevel = 0; - currentSubLevel = 0; - - dataset = Lang.load(items.parser, baseUrl, "words.json", "content-$LOCALE.json") - if(!dataset) { - // English fallback - items.background.englishFallback = true - dataset = Lang.load(items.parser, baseUrl, "words.json", "content-en.json") - } else { - items.background.englishFallback = false - } - - lessons = Lang.getAllLessons(dataset) - maxLevel = lessons.length - - initLevel(); -} - -function stop() { -} - -function initLevel() { - items.bar.level = currentLevel + 1; - - var currentLesson = lessons[currentLevel] - wordList = Lang.getLessonWords(dataset, currentLesson); - Core.shuffle(wordList); - - maxSubLevel = wordList.length; - items.score.numberOfSubLevels = maxSubLevel; - items.score.visible = true - - subLevelsLeft = [] - for(var i in wordList) - subLevelsLeft.push(i) - initSubLevel() -} - -function initSubLevel() { - // initialize sublevel - if(items.score.currentSubLevel < items.score.numberOfSubLevels) - items.score.currentSubLevel = currentSubLevel + 1; - else - items.score.visible = false - - items.goodWordIndex = subLevelsLeft.pop() - items.goodWord = wordList[items.goodWordIndex] - - var selectedWords = [] - selectedWords.push(items.goodWord.translatedTxt) - for (var i = 0; i < wordList.length; i++) { - if(wordList[i].translatedTxt !== selectedWords[0]) - selectedWords.push(wordList[i].translatedTxt) - - if(selectedWords.length > 4) - break - } - // Push the result in the model - items.wordListModel.clear(); - Core.shuffle(selectedWords); - for (var j = 0; j < selectedWords.length; j++) { - items.wordListModel.append({"word": selectedWords[j] }) - } - items.wordImage.changeSource("qrc:/gcompris/data/" + items.goodWord.image) -} - -function nextLevel() { - if(maxLevel <= ++currentLevel ) { - currentLevel = 0 - } - currentSubLevel = 0; - initLevel(); -} - -function previousLevel() { - if(--currentLevel < 0) { - currentLevel = maxLevel - 1 - } - currentSubLevel = 0; - initLevel(); -} - -function nextSubLevel() { - ++currentSubLevel - if(subLevelsLeft.length === 0) { - items.bonus.good("smiley") - } else { - initSubLevel(); - } -} - -// Append to the queue of words for the sublevel the error -function badWordSelected(wordIndex) { - if (subLevelsLeft[0] != wordIndex) - subLevelsLeft.unshift(wordIndex); -} diff --git a/src/activities/intro_gravity/IntroGravity.qml b/src/activities/intro_gravity/IntroGravity.qml index 9cc21cbd8..356e7a423 100644 --- a/src/activities/intro_gravity/IntroGravity.qml +++ b/src/activities/intro_gravity/IntroGravity.qml @@ -1,238 +1,238 @@ /* GCompris - intro_gravity.qml * * Copyright (C) 2015 Siddhesh suthar * * Authors: * Bruno Coudoin and Matilda Bernard (GTK+ version) * Siddhesh suthar (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.2 import GCompris 1.0 import "../../core" import "intro_gravity.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property int oldWidth: width onWidthChanged: { // Reposition planets and asteroids, same for height Activity.repositionObjectsOnWidthChanged(width / oldWidth) oldWidth = width } property int oldHeight: height onHeightChanged: { // Reposition planets and asteroids, same for height Activity.repositionObjectsOnHeightChanged(height / oldHeight) oldHeight = height } pageComponent: Image { id: background anchors.fill: parent source: Activity.url+"background.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias planetLeft: planetLeft property alias planetRight: planetRight property alias scaleLeft: planetLeft.value property alias scaleRight: planetRight.value property alias spaceship: spaceship property alias shuttle: shuttle property alias shuttleMotion: shuttleMotion property alias timer: timer property alias arrow: arrow property alias asteroidCreation: asteroidCreation property GCAudio audioEffects: activity.audioEffects - property double distLeft: Math.abs(spaceshipX) - property double distRight: Math.abs(background.width - spaceshipX) + property double distLeft: Math.abs(spaceshipX / ApplicationInfo.ratio) + property double distRight: Math.abs((background.width - spaceshipX) / ApplicationInfo.ratio) property double forceLeft: (Math.pow(scaleLeft, 2) / Math.pow(distLeft, 2)) * Math.pow(10, 6) property double forceRight: (Math.pow(scaleRight, 2) / Math.pow(distRight, 2)) * Math.pow(10, 6) // the center value for the spaceship property double spaceshipX property double spaceshipY: parent.height / 2 } onStart: Activity.start(items,message) onStop: Activity.stop() DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.initLevel() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } IntroMessage { id: message onIntroDone: { items.timer.start() items.asteroidCreation.start() items.shuttleMotion.restart() } intro: [ qsTr("Gravity is universal and Newton's law of universal gravitation extends gravity" +" beyond earth. This force of gravitational attraction is directly dependent" +" upon the masses of both objects and inversely proportional to" +" the square of the distance that separates their centers."), qsTr("Since the gravitational force is directly proportional to the mass of both interacting " +"objects, more massive objects will attract each other with a greater gravitational " +"force. So as the mass of either object increases, the force of gravitational " +"attraction between them also increases"), qsTr("But this force is inversely proportional to the square of the separation distance " +"between the two interacting objects, more separation distance will " +"result in weaker gravitational forces."), qsTr("Your goal is to let Tux's spaceship move by changing the mass " +"of its surrounding planets. Don't get too close to the planets " +"or you will crash on them. " +"The arrow indicates the direction of the force on your ship."), qsTr("Avoid the asteroid and join the space " +"shuttle to win.") ] z: 20 anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } } Planet { id: planetLeft x: 0 width: parent.width * 0.5 height: parent.height planetSource: Activity.url + "saturn.svg" planetWidth: 240 * ApplicationInfo.ratio isLeft: true } Planet { id: planetRight x: parent.width / 2 width: parent.width * 0.5 height: parent.height planetSource: Activity.url + "neptune.svg" planetWidth: 184 * ApplicationInfo.ratio isLeft: false } Image { id: spaceship source: Activity.url + "tux_spaceship.svg" sourceSize.width: 120 * ApplicationInfo.ratio x: items.spaceshipX - width / 2 y: items.spaceshipY - height / 2 } // line to show force magnitude and direction Image { id: arrow visible: !message.displayed && width > 6 && timer.running x: items.forceLeft < items.forceRight ? items.spaceshipX : items.spaceshipX - width y: spaceship.y - 80 z: 11 sourceSize.width: 120 * 10 * ApplicationInfo.ratio width: Math.min(background.width / 4, Math.abs(items.forceLeft - items.forceRight) * 6) height: 40 * ApplicationInfo.ratio source: Activity.url +"arrow.svg" rotation: items.forceLeft > items.forceRight ? 0 : 180 Behavior on rotation { NumberAnimation{ duration: 100 } } Behavior on width { NumberAnimation{ duration: 100 } } } Image { id: shuttle source: Activity.url + "space_shuttle.svg" sourceSize.width: 80 * ApplicationInfo.ratio z: 10 NumberAnimation { id: shuttleMotion target: shuttle property: "y" to: 0 - shuttle.height duration: 40000 / (Activity.currentLevel+1) } } Timer { id: timer interval: 16 running: false repeat: true onTriggered: { Activity.movespaceship() Activity.handleCollisionWithAsteroid() } } Timer { id: asteroidCreation running: false repeat: true interval: 10200 - (bar.level * 200) onTriggered: Activity.createAsteroid() } } } diff --git a/src/activities/intro_gravity/intro_gravity.js b/src/activities/intro_gravity/intro_gravity.js index 0ef24bb96..68783b7d9 100644 --- a/src/activities/intro_gravity/intro_gravity.js +++ b/src/activities/intro_gravity/intro_gravity.js @@ -1,219 +1,220 @@ /* GCompris - intro_gravity.js * * Copyright (C) 2015 Siddhesh suthar * * Authors: * Bruno Coudoin and Matilda Bernard (GTK+ version) * Siddhesh suthar (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo .import "qrc:/gcompris/src/core/core.js" as Core var url = "qrc:/gcompris/src/activities/intro_gravity/resource/" var currentLevel = 0; var numberOfLevel = 4; // delta move var move //array of created asteroids var asteroids = new Array; var asteroidsErased = new Array; var minDuration = 20000 var asteroidCounter var items; var message; function start(items_,message_) { items = items_ currentLevel = 0 message = message_ initLevel() } function stop() { items.timer.stop() items.asteroidCreation.stop() items.shuttleMotion.stop() destroyAsteroids(asteroids) destroyAsteroids(asteroidsErased) asteroidCounter = 0 } function initLevel() { items.bar.level = currentLevel + 1 stop() items.scaleLeft = items.planetLeft.minimumValue + 0.2 * currentLevel items.scaleRight = items.planetRight.minimumValue + 0.2 * currentLevel items.spaceship.source = url + "tux_spaceship.svg" items.spaceshipX = items.background.width / 2 move = 0 items.shuttle.x = Math.random() > 0.5 ? items.background.width * 0.2 : items.background.width * 0.7 items.shuttle.y = items.background.height + items.shuttle.height if(items.bar.level != 1 ) { items.timer.start() items.asteroidCreation.start() items.shuttleMotion.restart() message.index = -1 } else { message.index = 0 } } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function repositionObjectsOnWidthChanged(factor) { if(items) { initLevel() } for(var i = asteroids.length - 1; i >= 0 ; --i) { var asteroid = asteroids[i]; } } function repositionObjectsOnHeightChanged(factor) { if(items ) { initLevel() } for(var i = asteroids.length - 1; i >= 0 ; --i) { var asteroid = asteroids[i]; } } // functions to create and handle asteroids function createAsteroid() { var asteroidComponent = Qt.createComponent("qrc:/gcompris/src/activities/intro_gravity/Asteroid.qml"); var ImageUrl = url + "asteroid" + Math.floor( Math.random() * 5 ) + ".png" var randomX = items.background.width * 0.2 + Math.random() * (items.background.width - items.background.width * 0.4) var fallDuration = minDuration - Math.floor(Math.random()* 1000 *(currentLevel+1)) var asteroid = asteroidComponent.createObject( items.background, { "source" : ImageUrl, "x": randomX, "y": 50, - "fallDuration": fallDuration + "fallDuration": fallDuration, + "scale": GCompris.ApplicationInfo.ratio }); if(asteroid === null) { console.log("error in creating the asteroid object") } asteroids.push(asteroid); asteroid.startMoving() asteroidCounter++ } function destroyAsteroids(asteroids) { for(var i = asteroids.length - 1; i >= 0 ; --i) { var asteroid = asteroids[i]; // Remove the asteroid asteroid.destroy() // Remove the element from list asteroids.splice(i,1) } } function movespaceship(){ move += ( items.forceRight - items.forceLeft) / 10000 * items.bar.level items.spaceshipX += move // Manage the crash case if(items.spaceshipX < 200 * GCompris.ApplicationInfo.ratio) crash() else if(items.spaceshipX > items.background.width - 200 * GCompris.ApplicationInfo.ratio) crash() // Manage to get into shuttle var shuttleY = items.shuttle.y + items.shuttle.height / 2 var shuttleX = items.shuttle.x + items.shuttle.width / 2 if(shuttleY > items.spaceshipY - items.spaceship.height / 2 && shuttleY < items.spaceshipY + items.spaceship.height / 2 && shuttleX > items.spaceshipX -items.spaceship.width / 2 && shuttleX < items.spaceshipX + items.spaceship.width / 2) { items.bonus.good("flower") items.spaceship.source = "" stop() } } function handleCollisionWithAsteroid() { if(asteroids !== undefined){ for(var i = asteroids.length -1 ; i >= 0 ; --i) { var asteroid = asteroids[i]; var x = asteroid.x + asteroid.width / 2 var y = asteroid.y + asteroid.height / 2 if(y > items.background.height) { asteroid.destroy() asteroids.splice(i,1) } else if(y > items.spaceshipY - items.spaceship.height / 2 && y < items.spaceshipY + items.spaceship.height / 2 && x > items.spaceshipX - items.spaceship.width / 2 && x < items.spaceshipX + items.spaceship.width / 2 ) { asteroid.destroy() asteroids.splice(i,1) crash() return } if(asteroidCounter == 4) { items.bonus.good("flower") stop() return } } } } function crash() { items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav") items.spaceship.source = url + "crash.svg" stop() } diff --git a/src/activities/lang/ActivityInfo.qml b/src/activities/lang/ActivityInfo.qml new file mode 100644 index 000000000..9fa6caee6 --- /dev/null +++ b/src/activities/lang/ActivityInfo.qml @@ -0,0 +1,43 @@ +/* GCompris - lang.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +import GCompris 1.0 + +ActivityInfo { + name: "lang/Lang.qml" + difficulty: 4 + icon: "lang/lang.svg" + author: "siddhesh suthar <siddhesh.it@gmail.com>" + demo: true + title: qsTr("Enrich your vocabulary") + description: qsTr("Complete language learning activities.") + goal: qsTr("Enrich your vocabulary in your native language or in a foreign one.") + prerequisite: qsTr("Reading") + manual: qsTr("Review a set of words. Each word is shown with a voice a text and an image.") + + "

" + qsTr("When done you are suggested an exercise in which, given the voice,") + + "

" + qsTr("you must find the right word. In the configuration you can select the language you want to learn.") + credit: qsTr("The images and voices come from the Art4Apps project: http://www.art4apps.org/.") + + "

" +qsTr(" You can also access this activity online on http://gcompris.net/activity/lang.") + section: "reading" + enabled: DownloadManager.isDataRegistered("words") || ApplicationInfo.isDownloadAllowed +} diff --git a/src/activities/lang/CMakeLists.txt b/src/activities/lang/CMakeLists.txt new file mode 100644 index 000000000..e52b64551 --- /dev/null +++ b/src/activities/lang/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/lang *.qml *.svg *.js resource/*) diff --git a/src/activities/lang/ImageReview.qml b/src/activities/lang/ImageReview.qml new file mode 100644 index 000000000..7b33b2ae0 --- /dev/null +++ b/src/activities/lang/ImageReview.qml @@ -0,0 +1,414 @@ +/* GCompris - lang.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +import QtQuick 2.1 +import GCompris 1.0 +import QtGraphicalEffects 1.0 + +import "../../core" +import "lang.js" as Activity +import "qrc:/gcompris/src/core/core.js" as Core + +Item { + id: imageReview + anchors.fill: parent + + property alias category: categoryText.text + property int wordListIndex // This is the current sub list of words + property var word: rootItem.opacity == 1 ? items.wordList[wordListIndex][score.currentSubLevel - 1] : undefined + // miniGames is list of miniGames + // first element is Activity name, + // second element is mode of miniGame + // third element is the qml to load + property var miniGames: [ + ["QuizActivity", 1, "Quiz.qml"], + ["QuizActivity", 2, "Quiz.qml"], + ["QuizActivity", 3, "Quiz.qml"], + ["SpellActivity", 1, "SpellIt.qml"] + ] + property var currentMiniGame + property var loadedItems + property bool started: rootItem.opacity == 1 + + // Start at last wordListIndex + function start() { + initLevel(wordListIndex) + } + + // Start the image review at wordList sublesson + function initLevel(wordListIndex_) { + wordListIndex = wordListIndex_ + score.currentSubLevel = 1 + score.numberOfSubLevels = items.wordList[wordListIndex].length + focus = true + forceActiveFocus() + miniGameLoader.source = "" + currentMiniGame = -1 + rootItem.opacity = 1 + } + + function stop() { + focus = false + rootItem.opacity = 0 + wordImage.changeSource('') + wordText.changeText('') + repeatItem.visible = false + } + + onWordChanged: { + if(word) { + if (Activity.playWord(word.voice)) { + word['hasVoice'] = true + repeatItem.visible = true + } else { + word['hasVoice'] = false + repeatItem.visible = false + } + wordImage.changeSource("qrc:/gcompris/data/" + word.image) + wordText.changeText(word.translatedTxt) + } + } + + // Cheat codes to access mini games directly + Keys.onPressed: { + if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_1)) { + initLevel(wordListIndex) + event.accepted = true + } + if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_2)) { + startMiniGame(0) + event.accepted = true + } + if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_3)) { + startMiniGame(1) + event.accepted = true + } + if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_4)) { + startMiniGame(2) + event.accepted = true + } + if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_5)) { + startMiniGame(3) + event.accepted = true + } + } + + Keys.onEscapePressed: { + Activity.launchMenuScreen() + } + + Keys.onLeftPressed: { + if( score.currentSubLevel > 1 ) { + imageReview.prevWord() + event.accepted = true + } + } + Keys.onRightPressed: { + imageReview.nextWord() + event.accepted = true + } + Keys.onSpacePressed: { + imageReview.nextWord() + event.accepted = true + } + Keys.onEnterPressed: { + imageReview.nextWord() + event.accepted = true + } + Keys.onReturnPressed: { + imageReview.nextWord() + event.accepted = true + } + + Item { + id: rootItem + anchors.fill: parent + opacity: 0 + Behavior on opacity { PropertyAnimation { duration: 200 } } + } + + Rectangle { + id: categoryTextbg + parent: rootItem + x: categoryText.x - 4 + y: categoryText.y - 4 + width: imageFrame.width + 8 + height: categoryText.height + 8 + color: "#5090ff" + border.color: "#000000" + border.width: 2 + radius: 16 + anchors { + horizontalCenter: parent.horizontalCenter + top: rootItem.top + topMargin: 4 * ApplicationInfo.ratio + } + + GCText { + id: categoryText + fontSize: mediumSize + font.weight: Font.DemiBold + width: parent.width - 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "white" + wrapMode: Text.WordWrap + } + } + + Image { + id: imageFrame + parent: rootItem + source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" + sourceSize.width: Math.min((parent.width - previousWordButton.width * 2) * 0.8, + (parent.height - categoryTextbg.height + - wordTextbg.height - bar.height) * 1.1) + anchors { + horizontalCenter: parent.horizontalCenter + top: categoryTextbg.bottom + margins: 10 * ApplicationInfo.ratio + } + z: 11 + + Image { + id: wordImage + // Images are not svg + width: Math.min(parent.width, parent.height) * 0.9 + height: width + anchors.centerIn: parent + + property string nextSource + function changeSource(nextSource_) { + nextSource = nextSource_ + animImage.start() + } + + SequentialAnimation { + id: animImage + PropertyAnimation { + target: wordImage + property: "opacity" + to: 0 + duration: 100 + } + PropertyAction { + target: wordImage + property: "source" + value: wordImage.nextSource + } + PropertyAnimation { + target: wordImage + property: "opacity" + to: 1 + duration: 100 + } + } + MouseArea { + anchors.fill: parent + enabled: rootItem.opacity == 1 + onClicked: { + activity.audioVoices.append(ApplicationInfo.getAudioFilePath(word.voice)) + } + } + } + + Image { + id: previousWordButton + source: "qrc:/gcompris/src/core/resource/bar_previous.svg"; + sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio + visible: score.currentSubLevel > 1 ? true : false + anchors { + right: parent.left + rightMargin: 30 + top: parent.top + topMargin: parent.height/2 - previousWordButton.height/2 + } + MouseArea { + id: previousWordButtonArea + anchors.fill: parent + onClicked: imageReview.prevWord() + } + } + + Image { + id: nextWordButton + source: "qrc:/gcompris/src/core/resource/bar_next.svg"; + sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio + anchors { + left: parent.right + leftMargin: 30 + top: parent.top + topMargin: parent.height/2 - previousWordButton.height/2 + } + MouseArea { + id: nextWordButtonArea + anchors.fill: parent + onClicked: imageReview.nextWord(); + } + } + } + + Rectangle { + id: wordTextbg + parent: rootItem + x: wordText.x - 4 + y: wordText.y - 4 + width: imageFrame.width + height: wordText.height + 4 + color: "#5090ff" + border.color: "#000000" + border.width: 2 + radius: 16 + anchors { + top: imageFrame.bottom + left: imageFrame.left + margins: 10 * ApplicationInfo.ratio + } + + GCText { + id: wordText + text: "" + fontSize: largeSize + font.weight: Font.DemiBold + width: parent.width + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "white" + wrapMode: Text.WordWrap + + property string nextWord + function changeText(nextWord_) { + nextWord = nextWord_ + animWord.start() + } + + SequentialAnimation { + id: animWord + PropertyAnimation { + target: wordText + property: "opacity" + to: 0 + duration: 100 + } + PropertyAction { + target: wordText + property: "text" + value: wordText.nextWord + } + PropertyAnimation { + target: wordText + property: "opacity" + to: 1 + duration: 100 + } + } + } + } + + BarButton { + id: repeatItem + parent: rootItem + source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; + sourceSize.width: 80 * ApplicationInfo.ratio + + z: 12 + anchors { + top: parent.top + left: parent.left + margins: 10 * ApplicationInfo.ratio + } + onClicked: Activity.playWord(imageReview.word.voice) + Behavior on opacity { PropertyAnimation { duration: 200 } } + } + + Score { + id: score + parent: rootItem + anchors.bottom: undefined + anchors.bottomMargin: 10 * ApplicationInfo.ratio + anchors.right: parent.right + anchors.rightMargin: 10 * ApplicationInfo.ratio + anchors.top: parent.top + } + + Loader { + id: miniGameLoader + width: parent.width + height: parent.height + anchors.fill: parent + asynchronous: false + } + + function nextPressed() { + if(currentMiniGame == 0) { + nextSubLevel() + } + } + + function nextWord() { + ++score.currentSubLevel; + + if(score.currentSubLevel == score.numberOfSubLevels + 1) { + nextMiniGame() + } + } + + function prevWord() { + --score.currentSubLevel + } + + function startMiniGame(miniGameIndex) { + currentMiniGame = miniGameIndex + var mode = miniGames[miniGameIndex][1]; + var itemToLoad = miniGames[miniGameIndex][2]; + + // preparing the wordList + var wordList = Core.shuffle(items.wordList[wordListIndex]).slice() + + miniGameLoader.source = itemToLoad; + var loadedItems = miniGameLoader.item + + rootItem.opacity = 0 + focus = false + + // Initiate the loaded item mini game + // Some Mini Games may not start because they miss voices + // In this case we try the next one + if(!loadedItems.init(loadedItems, wordList, mode)) + nextMiniGame() + } + + //called by a miniGame when it is won + function nextMiniGame() { + if(currentMiniGame < miniGames.length - 1) { + startMiniGame(++currentMiniGame) + } else { + Activity.markProgress() + if(wordListIndex < items.wordList.length - 1) { + initLevel(wordListIndex + 1) + } else { + Activity.launchMenuScreen() + } + } + } +} diff --git a/src/activities/lang/Lang.qml b/src/activities/lang/Lang.qml new file mode 100644 index 000000000..c3e146b65 --- /dev/null +++ b/src/activities/lang/Lang.qml @@ -0,0 +1,284 @@ +/* GCompris - lang.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +import QtQuick 2.1 +import GCompris 1.0 +import QtGraphicalEffects 1.0 + +import "../../core" +import "lang.js" as Activity +import "spell_it.js" as SpellActivity +import "qrc:/gcompris/src/core/core.js" as Core + +ActivityBase { + id: activity + + onStart: focus = true + onStop: {} + + pageComponent: Image { + id: background + source: "qrc:/gcompris/src/activities/lang/resource/imageid-bg.svg" + fillMode: Image.PreserveAspectCrop + sourceSize.width: parent.width + + readonly property string wordsResource: "data2/words/words.rcc" + property bool englishFallback: false + property bool downloadWordsNeeded: false + + signal start + signal stop + signal voiceError + signal voiceDone + + Component.onCompleted: { + activity.start.connect(start) + activity.stop.connect(stop) + } + + // Add here the QML items you need to access in javascript + QtObject { + id: items + property Item main: activity.main + property GCAudio audioVoices: activity.audioVoices + property alias background: background + property alias bar: bar + property alias imageReview: imageReview + property alias parser: parser + property alias menuModel: menuScreen.menuModel + property var wordList + property alias menuScreen: menuScreen + property alias englishFallbackDialog: englishFallbackDialog + property string locale: 'system' + property alias dialogActivityConfig: dialogActivityConfig + } + + function handleResourceRegistered(resource) + { + if (resource == wordsResource) + Activity.start(); + } + + onStart: { + Activity.init(items) + dialogActivityConfig.getInitialConfiguration() + + activity.audioVoices.error.connect(voiceError) + activity.audioVoices.done.connect(voiceDone) + + // check for words.rcc: + if (DownloadManager.isDataRegistered("words")) { + // words.rcc is already registered -> start right away + Activity.start(); + } else if(DownloadManager.haveLocalResource(wordsResource)) { + // words.rcc is there -> register old file first + if (DownloadManager.registerResource(wordsResource)) + Activity.start(items); + else // could not register the old data -> react to a possible update + DownloadManager.resourceRegistered.connect(handleResourceRegistered); + // then try to update in the background + DownloadManager.updateResource(wordsResource); + } else { + // words.rcc has not been downloaded yet -> ask for download + downloadWordsNeeded = true + } + } + + onStop: { + DownloadManager.resourceRegistered.disconnect(handleResourceRegistered); + dialogActivityConfig.saveDatainConfiguration() + Activity.stop() + } + + JsonParser { + id: parser + onError: console.error("lang: Error parsing json: " + msg); + } + + MenuScreen { + id: menuScreen + } + + ImageReview { + id: imageReview + } + + DialogHelp { + id: dialogHelp + onClose: home() + } + + Bar { + id: bar + anchors.bottom: keyboardArea.top + content: BarEnumContent { value: + menuScreen.started ? help | home | config + : help | home } + onHelpClicked: { + displayDialog(dialogHelp) + } + onHomeClicked: { + if(!items.menuScreen.started && !items.imageReview.started) + // We're in a mini game, start imageReview + items.imageReview.start() + else if(items.imageReview.started) + // Leave imageReview + Activity.launchMenuScreen() + else + home() + } + onConfigClicked: { + dialogActivityConfig.active = true + dialogActivityConfig.setDefaultValues() + displayDialog(dialogActivityConfig) + } + } + + // This is a stop to hold the virtual keyboard from a mini game + Row { + id: keyboardArea + anchors.bottom: parent.bottom + width: parent.width + } + + Loader { + id: englishFallbackDialog + sourceComponent: GCDialog { + parent: activity.main + message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + + qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("http://l10n.kde.org/") + + "

" + + qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") + onClose: background.englishFallback = false + } + anchors.fill: parent + focus: true + active: background.englishFallback + onStatusChanged: if (status == Loader.Ready) item.start() + } + + Loader { + id: downloadWordsDialog + sourceComponent: GCDialog { + parent: activity.main + message: qsTr("The images for this activity are not yet installed.") + button1Text: qsTr("Download the images") + onClose: background.downloadWordsNeeded = false + onButton1Hit: { + DownloadManager.resourceRegistered.connect(handleResourceRegistered); + DownloadManager.downloadResource(wordsResource) + var downloadDialog = Core.showDownloadDialog(activity, {}); + } + } + anchors.fill: parent + focus: true + active: background.downloadWordsNeeded + onStatusChanged: if (status == Loader.Ready) item.start() + } + + DialogActivityConfig { + id: dialogActivityConfig + currentActivity: activity + content: Component { + Item { + property alias localeBox: localeBox + height: column.height + + property alias availableLangs: langs.languages + LanguageList { + id: langs + } + + Column { + id: column + spacing: 10 + width: parent.width + + Flow { + spacing: 5 + width: dialogActivityConfig.width + GCComboBox { + id: localeBox + model: langs.languages + background: dialogActivityConfig + width: dialogActivityConfig.width + label: qsTr("Select your locale") + } + } + } + } + } + + onLoadData: { + if(!dataToSave) + return + + if(dataToSave['locale']) { + items.locale = dataToSave["locale"]; + } + } + onSaveData: { + // Save the lessons status on the current locale + var oldLocale = items.locale + dataToSave[ApplicationInfo.getVoicesLocale(items.locale)] = + Activity.lessonsToSavedProperties(dataToSave) + + if(!dialogActivityConfig.loader.item) + return + + var newLocale = + dialogActivityConfig.configItem.availableLangs[ + dialogActivityConfig.loader.item.localeBox.currentIndex].locale; + // Remove .UTF-8 + if(newLocale.indexOf('.') != -1) { + newLocale = newLocale.substring(0, newLocale.indexOf('.')) + } + dataToSave['locale'] = newLocale + items.locale = newLocale; + + // Restart the activity with new information + if(oldLocale !== newLocale) { + Activity.stop() + Activity.start(); + } + } + + + function setDefaultValues() { + var localeUtf8 = items.locale; + if(items.locale != "system") { + localeUtf8 += ".UTF-8"; + } + + for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { + if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { + dialogActivityConfig.loader.item.localeBox.currentIndex = i; + break; + } + } + } + onClose: home() + } + } + +} diff --git a/src/activities/lang/MenuScreen.qml b/src/activities/lang/MenuScreen.qml new file mode 100644 index 000000000..bfdb7237c --- /dev/null +++ b/src/activities/lang/MenuScreen.qml @@ -0,0 +1,241 @@ +/* GCompris - MenuScreen.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/import QtQuick 2.1 +import GCompris 1.0 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 1.2 + +import "../../core" +import "lang.js" as Activity + +Image { + id: menuScreen + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: Activity.baseUrl + "imageid-bg.svg" + sourceSize.width: parent.width + opacity: 0 + + property alias menuModel: menuModel + property bool keyboardMode: false + property bool started: opacity == 1 + + Behavior on opacity { PropertyAnimation { duration: 200 } } + + function start() { + focus = true + forceActiveFocus() + menuGrid.currentIndex = 0 + opacity = 1 + } + + function stop() { + focus = false + opacity = 0 + } + + Keys.onEscapePressed: { + home() + } + + Keys.onPressed: { + if(event.key === Qt.Key_Space) { + menuGrid.currentItem.selectCurrentItem() + event.accepted = true + } + if(event.key === Qt.Key_Enter) { + menuGrid.currentItem.selectCurrentItem() + event.accepted = true + } + if(event.key === Qt.Key_Return) { + menuGrid.currentItem.selectCurrentItem() + event.accepted = true + } + if(event.key === Qt.Key_Left) { + menuGrid.moveCurrentIndexLeft() + event.accepted = true + } + if(event.key === Qt.Key_Right) { + menuGrid.moveCurrentIndexRight() + event.accepted = true + } + if(event.key === Qt.Key_Up) { + menuGrid.moveCurrentIndexUp() + event.accepted = true + } + if(event.key === Qt.Key_Down) { + menuGrid.moveCurrentIndexDown() + event.accepted = true + } + } + + Keys.onReleased: { + keyboardMode = true + event.accepted = false + } + + // Activities + property int iconWidth: 190 * ApplicationInfo.ratio + property int iconHeight: 190 * ApplicationInfo.ratio + + property int levelCellWidth: background.width / Math.floor(background.width / iconWidth ) + property int levelCellHeight: iconHeight * 1.3 + + ListModel { + id: menuModel + } + + GridView { + id: menuGrid + layer.enabled: true + + anchors { + fill: parent + bottomMargin: bar.height + } + cellWidth: levelCellWidth + cellHeight: levelCellHeight + clip: true + model: menuModel + keyNavigationWraps: true + property int spacing: 10 + + + delegate: Item { + id: delegateItem + width: levelCellWidth - menuGrid.spacing + height: levelCellHeight - menuGrid.spacing + property string sectionName: name + + Rectangle { + id: activityBackground + width: levelCellWidth - menuGrid.spacing + height: levelCellHeight - menuGrid.spacing + anchors.horizontalCenter: parent.horizontalCenter + color: "white" + opacity: 0.5 + } + Image { + id: containerImage + source: "qrc:/gcompris/data/"+ image; + anchors.top: activityBackground.top + anchors.horizontalCenter: parent.horizontalCenter + sourceSize.height: iconHeight + anchors.margins: 5 + + GCText { + id: title + anchors.top: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + width: activityBackground.width + fontSizeMode: Text.Fit + minimumPointSize: 7 + fontSize: regularSize + elide: Text.ElideRight + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: name + } + ProgressBar { + id: progressLang + anchors.top: title.bottom + anchors.topMargin: ApplicationInfo.ratio * 4 + anchors.horizontalCenter: parent.horizontalCenter + width: activityBackground.width + maximumValue: wordCount + minimumValue: 0 + value: progress + orientation: Qt.Horizontal + } + + } + + ParticleSystemStarLoader { + id: particles + anchors.fill: activityBackground + } + MouseArea { + anchors.fill: activityBackground + enabled: menuScreen.opacity == 1 + onClicked: selectCurrentItem() + } + + function selectCurrentItem() { + particles.burst(50) + Activity.initLevel(index) + } + + Image { + source: "qrc:/gcompris/src/activities/menu/resource/" + + ( favorite ? "all.svg" : "all_disabled.svg" ); + anchors { + top: parent.top + right: parent.right + rightMargin: 4 * ApplicationInfo.ratio + } + sourceSize.width: iconWidth * 0.25 + visible: ApplicationSettings.sectionVisible + + MouseArea { + anchors.fill: parent + onClicked: { + menuModel.get(index)['favorite'] = !menuModel.get(index)['favorite'] + } + } + } + + } //delegate close + + highlight: Rectangle { + width: levelCellWidth - menuGrid.spacing + height: levelCellHeight - menuGrid.spacing + color: "#AA41AAC4" + border.width: 3 + border.color: "black" + visible: menuScreen.keyboardMode + Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } + Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } + } + + Rectangle{ + id: menusMask + visible: false + anchors.fill: menuGrid + gradient: Gradient { + GradientStop { position: 0.0; color: "#FFFFFFFF" } + GradientStop { position: 0.92; color: "#FFFFFFFF" } + GradientStop { position: 0.96; color: "#00FFFFFF"} + } + } + + layer.effect: OpacityMask { + id: activitiesOpacity + source: menuGrid + maskSource: menusMask + anchors.fill: menuGrid + } + + } // grid view close + +} diff --git a/src/activities/lang/Quiz.qml b/src/activities/lang/Quiz.qml new file mode 100644 index 000000000..b9b1ed88c --- /dev/null +++ b/src/activities/lang/Quiz.qml @@ -0,0 +1,283 @@ +/* GCompris - Quiz.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +import QtQuick 2.1 +import GCompris 1.0 +import QtGraphicalEffects 1.0 + +import "../../core" +import "lang.js" as Activity +import "quiz.js" as QuizActivity + +Item { + id: quiz + opacity: 0 + + property alias background: background + property alias bonus: bonus + property alias score: score + property alias wordImage: wordImage + property alias imageFrame: imageFrame + property alias wordListModel: wordListModel + property alias wordListView: wordListView + property alias parser: parser + property variant goodWord + property bool horizontalLayout: background.width > background.height + + function init(loadedItems_, wordList_, mode_) { + opacity = 1 + loadedItems_.forceActiveFocus() + return QuizActivity.init(loadedItems_, wordList_, mode_) + } + + onGoodWordChanged: Activity.playWord(goodWord.voice) + + Behavior on opacity { PropertyAnimation { duration: 200 } } + + Image { + id: background + source: "qrc:/gcompris/src/activities/lang/resource/imageid-bg.svg" + fillMode: Image.PreserveAspectCrop + sourceSize.width: parent.width + height: parent.height + + property bool keyNavigation: false + + Keys.onEscapePressed: { + imageReview.start() + } + Keys.onRightPressed: { + keyNavigation = true + wordListView.incrementCurrentIndex() + } + Keys.onLeftPressed: { + keyNavigation = true + wordListView.decrementCurrentIndex() + } + Keys.onDownPressed: { + keyNavigation = true + wordListView.incrementCurrentIndex() + } + Keys.onUpPressed: { + keyNavigation = true + wordListView.decrementCurrentIndex() + } + Keys.onSpacePressed: { + keyNavigation = true + wordListView.currentItem.children[1].pressed() + } + Keys.onEnterPressed: { + keyNavigation = true + wordListView.currentItem.children[1].pressed() + } + Keys.onReturnPressed: { + keyNavigation = true + wordListView.currentItem.children[1].pressed() + } + + JsonParser { + id: parser + + onError: console.error("Lang: Error parsing json: " + msg); + } + + ListModel { + id: wordListModel + } + + Grid { + id: gridId + columns: quiz.horizontalLayout ? 2 : 1 + spacing: 10 * ApplicationInfo.ratio + anchors.fill: parent + anchors.margins: 10 * ApplicationInfo.ratio + + Item { + width: quiz.horizontalLayout + ? background.width * 0.40 + : background.width - gridId.anchors.margins * 2 + height: quiz.horizontalLayout + ? background.height - bar.height + : (background.height - bar.height) * 0.4 + + Image { + id: imageFrame + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" + sourceSize.width: quiz.horizontalLayout ? parent.width * 0.7 : parent.height * 1.2 + z: 11 + visible: QuizActivity.mode !== 3 + + Image { + id: wordImage + sourceSize.width: parent.width * 0.6 + + anchors { + centerIn: parent + margins: 0.05 + parent.width + } + property string nextSource + function changeSource(nextSource_) { + nextSource = nextSource_ + animImage.start() + } + + SequentialAnimation { + id: animImage + PropertyAnimation { + target: wordImage + property: "opacity" + to: 0 + duration: 100 + } + PropertyAction { + target: wordImage + property: "source" + value: wordImage.nextSource + } + PropertyAnimation { + target: wordImage + property: "opacity" + to: 1 + duration: 100 + } + } + MouseArea { + anchors.fill: parent + onClicked: Activity.playWord(goodWord.voice) + } + } + } + } + + + ListView { + id: wordListView + width: quiz.horizontalLayout + ? background.width * 0.55 + : background.width - gridId.anchors.margins * 2 + height: quiz.horizontalLayout + ? background.height - bar.height + : (background.height - bar.height) * 0.60 + spacing: 10 * ApplicationInfo.ratio + orientation: Qt.Vertical + verticalLayoutDirection: ListView.TopToBottom + interactive: false + model: wordListModel + + highlight: Rectangle { + width: wordListView.width + height: wordListView.buttonHeight + color: "lightsteelblue" + radius: 5 + visible: background.keyNavigation + y: wordListView.currentItem.y + Behavior on y { + SpringAnimation { + spring: 3 + damping: 0.2 + } + } + } + highlightFollowsCurrentItem: false + focus: true + keyNavigationWraps: true + + property int buttonHeight: height / wordListModel.count * 0.9 + + delegate: Item { + + id: wordListViewDelegate + + width: wordListView.width + height: wordListView.buttonHeight + anchors.right: parent.right + anchors.left: parent.left + + Image { + id: wordImageQuiz + width: height + height: wordListView.buttonHeight + source: "qrc:/gcompris/data/" + image + z: 7 + fillMode: Image.PreserveAspectFit + anchors.leftMargin: 5 * ApplicationInfo.ratio + visible: (QuizActivity.mode == 1) ? true : false // hide images after first mini game + } + + AnswerButton { + id: wordRectangle + width: parent.width * 0.6 + height: wordListView.buttonHeight + textLabel: translatedTxt + + anchors.left: wordImageQuiz.left + anchors.right: parent.right + + isCorrectAnswer: translatedTxt === quiz.goodWord.translatedTxt + onIncorrectlyPressed: { + // push the error to have it asked again + QuizActivity.remainingWords.unshift(quiz.goodWord); + QuizActivity.nextSubLevelQuiz(); + } + onCorrectlyPressed: { + QuizActivity.nextSubLevelQuiz(); + } + } + } + } + } + + BarButton { + id: repeatItem + source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; + sourceSize.width: 80 * ApplicationInfo.ratio + + z: 12 + anchors { + top: parent.top + left: parent.left + margins: 10 * ApplicationInfo.ratio + } + onClicked: Activity.playWord(goodWord.voice) + Behavior on opacity { PropertyAnimation { duration: 200 } } + } + + Score { + id: score + anchors.bottom: undefined + anchors.bottomMargin: 10 * ApplicationInfo.ratio + anchors.right: parent.right + anchors.rightMargin: 10 * ApplicationInfo.ratio + anchors.top: parent.top + } + + Bonus { + id: bonus + onWin: imageReview.nextMiniGame() + } + } +} diff --git a/src/activities/lang/SpellIt.qml b/src/activities/lang/SpellIt.qml new file mode 100644 index 000000000..75e5f770a --- /dev/null +++ b/src/activities/lang/SpellIt.qml @@ -0,0 +1,303 @@ +/* GCompris - SpellIt.qml +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +import QtQuick 2.1 +import GCompris 1.0 +import QtGraphicalEffects 1.0 + +import "../../core" +import "lang.js" as Activity +import "spell_it.js" as SpellActivity + +Item { + id: spellIt + opacity: 0 + + property alias background: background + property alias wordImage: wordImage + property alias imageFrame: imageFrame + property alias hintTextbg: hintTextbg + property alias hintText: hintText + property alias parser: parser + property alias answerbg: answerbg + property alias answer: answer + property alias ok: ok + property alias okMouseArea: okMouseArea + property alias bonus: bonus + property alias keyboard: keyboard + property alias score: score + property variant goodWord + property int goodWordIndex + property int maximumLengthAnswer + + function init(loadedItems_, wordList_, mode_) { + opacity = 1 + return SpellActivity.init(loadedItems_, wordList_, mode_); + } + + onGoodWordChanged: Activity.playWord(goodWord.voice) + + Behavior on opacity { PropertyAnimation { duration: 200 } } + + Keys.onEscapePressed: { + imageReview.start() + } + + Image { + id: background + source: "qrc:/gcompris/src/activities/lang/resource/imageid-bg.svg" + fillMode: Image.PreserveAspectCrop + sourceSize.width: parent.width + height: parent.height + + property bool horizontalLayout: background.width > background.height + + JsonParser { + id: parser + onError: console.error("Lang: Error parsing json: " + msg); + } + + Rectangle { + id: hintTextbg + x: hintText.x -4 + y: hintText.y -4 + width: imageFrame.width + height: hintText.height +4 + color: "#5090ff" + border.color: "#000000" + border.width: 2 + radius: 16 + anchors.top: parent.top + anchors.bottom: imageFrame.top + anchors.left: imageFrame.left + anchors.bottomMargin: 5 + + + GCText { + id: hintText + text: "" + fontSize: largeSize + font.weight: Font.DemiBold + width: parent.width + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: "white" + wrapMode: Text.WordWrap + + property string nextHint + function changeHint(nextHint_) { + nextHint = nextHint_ + animHint.start() + } + + SequentialAnimation { + id: animHint + PropertyAnimation { + target: hintText + property: "opacity" + to: 0 + duration: 100 + } + PropertyAction { + target: hintText + property: "text" + value: ""+ hintText.nextHint + } + PropertyAnimation { + target: hintText + property: "opacity" + to: 1 + duration: 100 + } + } + + } + } + + Image { + id: imageFrame + source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" + sourceSize.width: background.horizontalLayout ? parent.width * 0.9 : parent.height * 1.2 + width: background.width * 0.55 + height: (background.height - hintTextbg.height - answerbg.height + - keyboard.height - bar.height) * 0.8 + + anchors { + horizontalCenter: background.horizontalCenter + top: background.top + topMargin: (background.height) * 0.15 + } + z: 11 + + Image { + id: wordImage + sourceSize.width: Math.min(parent.width * 0.6, parent.height * 0.6) + + anchors { + centerIn: parent + margins: 0.05 + parent.width + } + property string nextSource + function changeSource(nextSource_) { + nextSource = nextSource_ + animImage.start() + } + + SequentialAnimation { + id: animImage + PropertyAnimation { + target: wordImage + property: "opacity" + to: 0 + duration: 100 + } + PropertyAction { + target: wordImage + property: "source" + value: wordImage.nextSource + } + PropertyAnimation { + target: wordImage + property: "opacity" + to: 1 + duration: 100 + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + Activity.playWord(goodWord.voice) + } + } + } + } + + Rectangle { + id: answerbg + x: answer.x -4 + y: answer.y -4 + width: imageFrame.width + height: answer.height +4 + color: "#5090ff" + border.color: "#000000" + border.width: 2 + radius: 16 + anchors { + top: imageFrame.bottom + left: imageFrame.left + topMargin: 20* ApplicationInfo.ratio + } + + TextInput { + id: answer + width: hintTextbg.width + height: hintTextbg.height + color: "white" + cursorVisible: true + focus: true + visible: true + horizontalAlignment: TextInput.AlignHCenter + verticalAlignment: TextInput.AlignVCenter + font.pointSize: hintText.pointSize + font.weight: Font.DemiBold + font.family: GCSingletonFontLoader.fontLoader.name + font.capitalization: ApplicationSettings.fontCapitalization + maximumLength: maximumLengthAnswer + onAccepted: { + answer.forceActiveFocus() + okMouseArea.clicked(okMouseArea) + } + } + } + + Image { + id: ok + source:"qrc:/gcompris/src/core/resource/bar_ok.svg" + sourceSize.width: 70 * ApplicationInfo.ratio + fillMode: Image.PreserveAspectFit + anchors { + top: imageFrame.bottom + topMargin: 10* ApplicationInfo.ratio + left: imageFrame.right + leftMargin: 10* ApplicationInfo.ratio + right: parent.right + } + MouseArea { + id: okMouseArea + anchors.fill: parent + hoverEnabled: true + onEntered: ok.scale = 1.1 + onClicked: { + SpellActivity.checkAnswer(answer.text) + } + onExited: ok.scale = 1 + } + } + + Bonus { + id: bonus + onWin: imageReview.nextMiniGame() + } + + } + + BarButton { + id: repeatItem + source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; + sourceSize.width: 80 * ApplicationInfo.ratio + + z: 12 + anchors { + top: parent.top + left: parent.left + margins: 10 * ApplicationInfo.ratio + } + onClicked: Activity.playWord(goodWord.voice) + Behavior on opacity { PropertyAnimation { duration: 200 } } + } + + Score { + id: score + anchors.bottom: undefined + anchors.bottomMargin: 10 * ApplicationInfo.ratio + anchors.right: parent.right + anchors.rightMargin: 10 * ApplicationInfo.ratio + anchors.top: parent.top + } + + VirtualKeyboard { + id: keyboard + parent: keyboardArea + anchors.bottom: undefined + anchors.horizontalCenter: undefined + width: parent.width + visible: ApplicationSettings.isVirtualKeyboard + + onKeypress: SpellActivity.processKeyPress(text) + onError: console.log("VirtualKeyboard error: " + msg); + } + +} diff --git a/src/activities/lang/lang.js b/src/activities/lang/lang.js new file mode 100644 index 000000000..d92c573b6 --- /dev/null +++ b/src/activities/lang/lang.js @@ -0,0 +1,192 @@ +/* GCompris - lang.js +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/.pragma library +.import QtQuick 2.0 as Quick +.import GCompris 1.0 as GCompris +.import "qrc:/gcompris/src/core/core.js" as Core +.import "qrc:/gcompris/src/activities/lang/lang_api.js" as Lang + +var lessonModelIndex = 0; +var currentSubLevel = 0; +var items; +var baseUrl = "qrc:/gcompris/src/activities/lang/resource/"; +var dataset +var lessons +var maxWordInLesson = 12 + +function init(items_) { + items = items_ + lessonModelIndex = 0 + currentSubLevel = 0 +} + +function start() { + lessonModelIndex = 0; + currentSubLevel = 0; + items.imageReview.stop() + + var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) + + // register the voices for the locale + GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(locale)) + + dataset = Lang.load(items.parser, baseUrl, "words.json", + "content-"+ locale +".json") + + // If dataset is empty, we try to load from short locale + // and if not present again, we switch to default one + var localeUnderscoreIndex = locale.indexOf('_') + if(!dataset) { + var localeShort; + // We will first look again for locale xx (without _XX if exist) + if(localeUnderscoreIndex > 0) { + localeShort = locale.substring(0, localeUnderscoreIndex) + } else { + localeShort = locale; + } + dataset = Lang.load(items.parser, baseUrl, "words.json", + "content-"+localeShort+ ".json") + } + + // If still dataset is empty then fallback to english + if(!dataset) { + // English fallback + items.background.englishFallback = true + dataset = Lang.load(items.parser, baseUrl, "words.json", "content-en.json") + } else { + items.background.englishFallback = false + } + + // We have to keep it because we can't access content from the model + lessons = Lang.getAllLessons(dataset) + addPropertiesToLessons(lessons) + + items.menuModel.clear() + items.menuModel.append(lessons) + savedPropertiesToLessons(items.dialogActivityConfig.dataToSave) + sortByFavorites(); + + items.menuScreen.start() +} + +// Insert our specific properties in the lessons +function addPropertiesToLessons(lessons) { + for (var i in lessons) { + // Ceil the wordCount to a maxWordInLesson count + lessons[i]['wordCount'] = + Math.ceil(Lang.getLessonWords(dataset, lessons[i]).length / maxWordInLesson) + * maxWordInLesson + lessons[i]['image'] = lessons[i].content[0].image + lessons[i]['progress'] = 0 + lessons[i]['favorite'] = false + // We need to keep a back reference from the model to the lessons array + lessons[i]['lessonIndex'] = i + } +} + +// Return a new json that contains all the properties we have to save +function lessonsToSavedProperties() { + var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) + var props = {} + for(var i = 0; i < items.menuModel.count; i++) { + var lesson = items.menuModel.get(i) + props[lesson.name] = { + 'favorite': lesson['favorite'], + 'progress': lesson['progress'] + } + } + return props +} + +// Update the lessons based on a previous saving +function savedPropertiesToLessons(dataToSave) { + var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) + var props = dataToSave[locale] + for(var i = 0; i < items.menuModel.count; i++) { + var lesson = items.menuModel.get(i) + if(props && props[lesson.name]) { + lesson['favorite'] = props[lesson.name].favorite + lesson['progress'] = props[lesson.name].progress + } else { + lesson['favorite'] = false + lesson['progress'] = 0 + } + } +} + +function stop() { +} + +function initLevel(lessonModelIndex_) { + lessonModelIndex = lessonModelIndex_ + var lessonIndex = items.menuModel.get(lessonModelIndex).lessonIndex + + var flatWordList = Lang.getLessonWords(dataset, lessons[lessonIndex]); + // We have to split the works in chunks of maxWordInLesson + items.wordList = [] + var i = 0 + while(flatWordList.length > 0) { + items.wordList[i++] = Core.shuffle(flatWordList.splice(0, maxWordInLesson)); + } + // If needed complete the last set to have maxWordInLesson items in it + // We pick extra items from the head of the list + if(items.wordList[i-1].length != maxWordInLesson) { + var flatWordList = Lang.getLessonWords(dataset, lessons[lessonIndex]); + var lastLength = items.wordList[i-1].length + items.wordList[i-1] = + items.wordList[i-1].concat(flatWordList.splice(0, maxWordInLesson - lastLength)) + } + + items.imageReview.category = lessons[lessonIndex].name + // Calc the sublevel to start with + var subLevel = Math.floor(items.menuModel.get(lessonModelIndex)['progress'] / maxWordInLesson) + if(subLevel >= items.wordList.length) + // Level done, start again at level 0 + subLevel = 0 + + items.menuScreen.stop() + items.imageReview.initLevel(subLevel) +} + +function launchMenuScreen() { + items.imageReview.stop() + items.menuScreen.start() +} + +function sortByFavorites() { + for(var i = 0; i < items.menuModel.count; i++) { + if(items.menuModel.get(i)['favorite']) + items.menuModel.move(i, 0, 1); + } +} + +function markProgress() { + // We count progress as a number of image learnt from the lesson start + items.menuModel.get(lessonModelIndex)['progress'] += maxWordInLesson +} + +function playWord(word) { + var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) + return items.audioVoices.append( + GCompris.ApplicationInfo.getAudioFilePathForLocale(word, locale)) +} diff --git a/src/activities/imageid/imageid.svg b/src/activities/lang/lang.svg similarity index 100% rename from src/activities/imageid/imageid.svg rename to src/activities/lang/lang.svg diff --git a/src/activities/imageid/lang_api.js b/src/activities/lang/lang_api.js similarity index 88% rename from src/activities/imageid/lang_api.js rename to src/activities/lang/lang_api.js index 0d693a981..846123bc7 100644 --- a/src/activities/imageid/lang_api.js +++ b/src/activities/lang/lang_api.js @@ -1,109 +1,107 @@ /* GCompris - lang_api.js * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (bruno.coudoin@gcompris.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ .pragma library .import GCompris 1.0 as GCompris .import "qrc:/gcompris/src/core/core.js" as Core var contentText function validateDataset(levels) { return true; } function load(parser, baseUrl, datasetFilename, translationFilename) { var datasetUrl = baseUrl + "/" + datasetFilename; var dataset = parser.parseFromUrl(datasetUrl, validateDataset); if (dataset === null) { console.error("Lang: Invalid dataset, can't continue: " + datasetUrl); return; } dataset['contentText'] = loadContent(parser, - GCompris.ApplicationInfo.getLocaleFilePath(baseUrl + "/" + translationFilename)) + GCompris.ApplicationInfo.getLocaleFilePath(baseUrl + "/" + translationFilename)) if(!dataset['contentText']) { return null } return dataset } function loadContent(parser, datasetUrl) { var dataset = parser.parseFromUrl(datasetUrl, validateDataset); if (dataset === null) { console.error("Lang: Invalid dataset, can't continue: " + datasetUrl); return; } return dataset } function getChapter(dataset, chapter) { return dataset[chapter] } // Return a datamodel for the chapter suitable for creating a chapter selector function getChapterModel(dataset) { var chapters = [] for (var c = 0; c < dataset.length; c++) { chapters.push( {'name': dataset[c].name, - 'image': dataset[c].content[0].content[0].image, - 'index': c + 'image': dataset[c].content[0].content[0].image, + 'index': c }) } return chapters } function getLesson(dataset, chapter, lesson) { return chapter.content[lesson] } function getAllLessons(dataset) { var lessons = [] for (var c in dataset) { for (var l in dataset[c].content) { var lesson = getLesson(dataset, dataset[c], l) lessons.push(lesson) } } return lessons } function getLessonWords(dataset, lesson) { var wordList = lesson.content // Fill up the lesson with the translated text var allWords = [] for (var k in wordList) { var word = wordList[k] - - word['translatedTxt'] = dataset.contentText[word.voice.substr(word.voice.lastIndexOf("/")+1).replace("$CA", "ogg")]; - + word['translatedTxt'] = dataset.contentText[ + word.voice.substr(word.voice.lastIndexOf("/")+1).replace("$CA", "ogg")]; if(word['translatedTxt']) allWords.push(word) } - return allWords } diff --git a/src/activities/lang/quiz.js b/src/activities/lang/quiz.js new file mode 100644 index 000000000..7db7e3f20 --- /dev/null +++ b/src/activities/lang/quiz.js @@ -0,0 +1,99 @@ +/* GCompris - quiz.js +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +.pragma library +.import QtQuick 2.0 as Quick +.import GCompris 1.0 as GCompris +.import "qrc:/gcompris/src/core/core.js" as Core +.import "qrc:/gcompris/src/activities/lang/lang_api.js" as Lang + +var quizItems +var wordList +var remainingWords +var mode + +// @return true if the quiz was ran +function init(loadedItems_, wordList_, mode_) { + quizItems = loadedItems_ + wordList = wordList_ + mode = mode_ + + quizItems.score.numberOfSubLevels = wordList.length + + if(mode == 3) { + quizItems.imageFrame.visible = false + // Remove words for which we don't have voice + for (var j = 0; j < wordList.length ; j++) { + if(!wordList[j].hasVoice) { + wordList.splice(j, 1) + j--; + } + } + } else { + quizItems.imageFrame.visible = true + } + + // Bails out if we don't have enough words to play + if(wordList.length < 2) { + return false + } + + quizItems.wordListView.forceActiveFocus() + remainingWords = Core.shuffle(wordList).slice() + nextQuiz(); + return true +} + +function nextQuiz() { + + quizItems.score.currentSubLevel = quizItems.score.numberOfSubLevels - remainingWords.length + 1 + + quizItems.goodWord = remainingWords.pop() + + var selectedWords = [] + selectedWords.push(quizItems.goodWord) + + // Pick 3 wrong words to complete the quiz + for (var i = 0; i < wordList.length; i++) { + if(wordList[i] !== quizItems.goodWord) { + selectedWords.push(wordList[i]) + } + if(selectedWords.length > 4) + break + } + + // Push the result in the model + selectedWords = Core.shuffle(selectedWords); + quizItems.wordListModel.clear(); + quizItems.wordListModel.append(selectedWords) + + quizItems.wordImage.changeSource("qrc:/gcompris/data/" + quizItems.goodWord.image) +} + +function nextSubLevelQuiz() { + if(remainingWords.length === 0) { + quizItems.bonus.good("smiley") + } else { + nextQuiz(); + } +} diff --git a/src/activities/imageid/resource/content-ca.json b/src/activities/lang/resource/content-ca.json similarity index 99% rename from src/activities/imageid/resource/content-ca.json rename to src/activities/lang/resource/content-ca.json index d91e3feca..9ccc6f276 100644 --- a/src/activities/imageid/resource/content-ca.json +++ b/src/activities/lang/resource/content-ca.json @@ -1,582 +1,582 @@ -{ +<{ "ate.ogg": "menjar", "bake.ogg": "cuinar", "bark.ogg": "bordar", "beg.ogg": "demanar", "bite.ogg": "mossegar", "blink.ogg": "picada d'ullet", "boil.ogg": "bullir", "break.ogg": "trencar", "call.ogg": "trucada", "catch.ogg": "pescar", "centipede.ogg": "centpeus", "chat.ogg": "parlar", "chop.ogg": "tallar", "clap.ogg": "picar de mans", "clean.ogg": "rentar", "crawl.ogg": "gatejar", "croak.ogg": "raucar", "cry.ogg": "plorar", "cut.ogg": "tallar", "dig.ogg": "cavar", "drank.ogg": "beure", "draw.ogg": "dibuix", "dream.ogg": "somiar", "drive.ogg": "conduir", "drool.ogg": "bavejar", "dry.ogg": "assecar", "eat.ogg": "menjar", "fell.ogg": "caure", "fetch.ogg": "informar", "find.ogg": "trobar", "float.ogg": "flotador", "fly.ogg": "volar", "freeze.ogg": "congelar", "growl.ogg": "grunyir", "hatch.ogg": "sortir de l'ou", "hear.ogg": "sentir", "howl.ogg": "udolar", "hug.ogg": "abraçar", "jump.ogg": "saltar", "kneel.ogg": "agenollar", "knit.ogg": "punt de mitja", "lick.ogg": "llepar", "look.ogg": "mirar-se", "meet.ogg": "trobar-se", "nap.ogg": "migdiada", "peck.ogg": "picotejar", "play.ogg": "jugar", "plow.ogg": "llaurar", "pounce.ogg": "saltar", "quarrel.ogg": "baralla", "read.ogg": "llegir", "rip.ogg": "estripar", "run.ogg": "córrer", "sat.ogg": "seure", "scared.ogg": "espantat", "scratch.ogg": "esgarrapar", "scream.ogg": "cridar", "scribble.ogg": "gargot", "see.ogg": "mirar", "shake.ogg": "sacsejar", "sing.ogg": "cantar", "sip.ogg": "xarrupar", "sit.ogg": "seure", "sleep.ogg": "dormir", "smell.ogg": "olorar", "smooch.ogg": "bessar", "sniff.ogg": "ensumar", "spill.ogg": "vessar", "splash.ogg": "doll d'aigua", "spray.ogg": "ruixar", "spread.ogg": "untar", "spring.ogg": "botar", "squat.ogg": "ajupir", "sting.ogg": "fibló", "stop.ogg": "aturar", "stretch.ogg": "estiraments", "study.ogg": "estudiar", "teach.ogg": "ensenyar", "tear.ogg": "estripar", "think.ogg": "pensar", "touch.ogg": "tocar", "wag.ogg": "moure la cua", "walk.ogg": "caminar", "wash.ogg": "banyar", "whisper.ogg": "xiuxiuejar", "win.ogg": "guanyar", "back.ogg": "esquena", "braid.ogg": "trena", "brain.ogg": "cervell", "bump.ogg": "bony", "chin.ogg": "barbeta", "ear.ogg": "orella", "face.ogg": "cara", "feet.ogg": "peu", "fist.ogg": "puny", "foot.ogg": "peu", "grin.ogg": "somriure", "hair.ogg": "cabell", "hand.ogg": "mà", "head.ogg": "cap", "hip.ogg": "maluc", "knee.ogg": "genoll", "lap.ogg": "a la falda", "lip.ogg": "llavis", "mouth.ogg": "boca", "neck.ogg": "coll", "nose.ogg": "nas", "scar.ogg": "cicatriu", "skin.ogg": "pell", "stare.ogg": "mirar fixament", "sweat.ogg": "suor", "teeth.ogg": "dents", "thigh.ogg": "cuixa", "throat.ogg": "gola", "wrist.ogg": "canell", "bib.ogg": "pitet", "cap.ogg": "gorra", "cape.ogg": "capa", "coat.ogg": "abric", "dress.ogg": "vestit", "glove.ogg": "guants", "hat.ogg": "barret", "patch.ogg": "pedaç", "scarf.ogg": "bufanda", "shirt.ogg": "camisa", "shoe.ogg": "sabata", "short.ogg": "pantalons curts", "skirt.ogg": "faldilla", "sleeve.ogg": "màniga", "smock.ogg": "brusa", "sneaker.ogg": "sabatilla deportiva", "sock.ogg": "mitjó", "suit.ogg": "vestit", "zoo.ogg": "zoològic", "danger.ogg": "perill", "frown.ogg": "envejar", "fun.ogg": "divertit", "mad.ogg": "ira", "rage.ogg": "ràbia", "sad.ogg": "trist", "smile.ogg": "somriure", "whisk.ogg": "batedora", "chief.ogg": "indi", "clown.ogg": "pallasso", "coach.ogg": "entrenador", "cowboy.ogg": "vaquer", "hunter.ogg": "caçador", "judge.ogg": "jutge", "knight.ogg": "cavaller", "lad.ogg": "mosso", "pope.ogg": "papa", "prince.ogg": "príncep", "princess.ogg": "princesa", "queen.ogg": "reina", "teacher.ogg": "professora", "vet.ogg": "veterinari", "boy.ogg": "noi", "bride.ogg": "núvia", "brother.ogg": "germà", "child.ogg": "nen", "dad.ogg": "pare", "friend.ogg": "amics", "girl.ogg": "dona", "gnome.ogg": "gnom", "mate.ogg": "amics", "men.ogg": "multitud", "pal.ogg": "col·legues", "athlete.ogg": "atleta", "ballet.ogg": "ballet", "camp.ogg": "acampar", "cheer.ogg": "animar", "climb.ogg": "escalada", "dance.ogg": "ballar", "dive.ogg": "immersió", "explore.ogg": "exploradors", "hike.ogg": "caminada", "hit.ogg": "pegar", "hop.ogg": "saltar", "jog.ogg": "fúting", "lift.ogg": "aixecar", "ran.ogg": "córrer", "ride.ogg": "anar amb bicicleta", "skate.ogg": "patinar", "ski.ogg": "esquiar", "sport.ogg": "esport", "swim.ogg": "nedar", "write.ogg": "escriure", "alligator.ogg": "caiman", "animal.ogg": "animals", "bait.ogg": "esquer", "bat.ogg": "ratpenat", "bee.ogg": "abella", "beetle.ogg": "escarabat", "bird.ogg": "ocell", "bug.ogg": "insecte", "camel.ogg": "camell", "cat.ogg": "gat", "chick.ogg": "pollet", "chicken.ogg": "pollastre", "chimp.ogg": "ximpanzé", "clam.ogg": "cloïssa", "claw.ogg": "urpa", "cow.ogg": "vaca", "crab.ogg": "cranc", "crocodile.ogg": "cocodril", "crow.ogg": "corb", "den.ogg": "cau", "dog.ogg": "gos", "dragon.ogg": "drac", "duck.ogg": "ànec", "fin.ogg": "aleta", "fish.ogg": "peix", "flies.ogg": "mosca", "fox.ogg": "guineu", "frog.ogg": "granota", "fur.ogg": "pell", "giraffe.ogg": "girafa", "goat.ogg": "cabra", "hen.ogg": "gallina", "herd.ogg": "ramat", "hound.ogg": "empaita", "lamb.ogg": "xai", "mane.ogg": "cabellera", "mice.ogg": "ratolins", "mole.ogg": "talp", "mosquito.ogg": "mosquit", "mule.ogg": "mula", "owl.ogg": "òliba", "ox.ogg": "bou", "paw.ogg": "pota", "pet.ogg": "mascota", "pig.ogg": "porc", "pug.ogg": "carlí", "puppy.ogg": "cadell", "rat.ogg": "rata", "shark.ogg": "tauró", "shell.ogg": "closca", "shrimp.ogg": "gamba", "skunk.ogg": "mofeta", "slime.ogg": "bava", "snail.ogg": "cargol", "snake.ogg": "serp", "spider.ogg": "aranya", "spike.ogg": "eriçó", "squid.ogg": "calamar", "squirrel.ogg": "esquirol", "starfish.ogg": "estrella de mar", "swan.ogg": "cigne", "tick.ogg": "paparra", "wing.ogg": "ala", "yum.ogg": "saborós", "bone.ogg": "os", "bread.ogg": "pans", "bun.ogg": "brioix", "cake.ogg": "pastís", "candy.ogg": "caramel", "cheese.ogg": "formatge", "chocolate.ogg": "xocolata", "cookie.ogg": "galetes", "crumb.ogg": "engrunes", "crust.ogg": "crosta", "drink.ogg": "tassa", "feast.ogg": "banquet", "flour.ogg": "farina", "food.ogg": "hamburguesa", "fudge.ogg": "dolços de xocolata", "gum.ogg": "xiclet", "ice.ogg": "glaçó", "juice.ogg": "suc", "lunch.ogg": "aperitiu", "pop.ogg": "crispetes", "rice.ogg": "arròs", "sandwich.ogg": "sandvitx", "sauce.ogg": "salsa", "snack.ogg": "mos", "spaghetti.ogg": "espagueti", "sprinkle.ogg": "espurnes", "stew.ogg": "guisat", "treat.ogg": "dolços", "cherry.ogg": "cireres", "fruit.ogg": "fruita", "lime.ogg": "llima", "orange.ogg": "taronja", "peach.ogg": "préssecs", "plum.ogg": "prunes", "bay.ogg": "badia", "beach.ogg": "platja", "cave.ogg": "cova", "cliff.ogg": "penya-segat", "cloud.ogg": "núvol", "cold.ogg": "fred", "dirt.ogg": "sòl", "dune.ogg": "duna", "earth.ogg": "la Terra", "fire.ogg": "foc", "flame.ogg": "flama", "garden.ogg": "jardí", "gem.ogg": "joia", "ground.ogg": "el terra", "heat.ogg": "calor", "hole.ogg": "forat", "lake.ogg": "llac", "land.ogg": "terreny", "ledge.ogg": "sortint", "mud.ogg": "fang", "night.ogg": "nit", "ocean.ogg": "oceà", "path.ogg": "camí", "rock.ogg": "roques", "sand.ogg": "sorra", "shore.ogg": "riba", "sky.ogg": "cel", "slope.ogg": "pendent", "smoke.ogg": "fum", "snow.ogg": "neu", "star.ogg": "estel", "stone.ogg": "pedres", "stream.ogg": "riu", "summer.ogg": "estiu", "sun.ogg": "sol", "time.ogg": "temps", "top.ogg": "al cim", "trail.ogg": "sender", "water.ogg": "aigua", "wind.ogg": "vent", "yarn.ogg": "fil", "angel.ogg": "àngel", "bit.ogg": "mica", "class.ogg": "classe", "price.ogg": "preu", "question.ogg": "pregunta", "quiz.ogg": "examen", "science.ogg": "ciència", "splatter.ogg": "esquitxada", "tune.ogg": "melodia", "branch.ogg": "branca", "bud.ogg": "brot", "bush.ogg": "arbust", "flower.ogg": "flor", "grass.ogg": "herba", "hay.ogg": "farratge", "hedge.ogg": "tanca", "lawn.ogg": "gespa", "pine.ogg": "pi", "plant.ogg": "plantes", "rose.ogg": "rosa", "seed.ogg": "llavors", "shrub.ogg": "arbust", "stem.ogg": "tija", "stick.ogg": "branca", "stump.ogg": "tronc", "tree.ogg": "arbre", "carrot.ogg": "pastanaga", "corn.ogg": "blat de moro", "cucumber.ogg": "cogombre", "mushroom.ogg": "bolet", "nut.ogg": "cacauet", "squash.ogg": "carbassó", "bank.ogg": "banc", "barn.ogg": "graner", "bridge.ogg": "pont", "cabin.ogg": "cabana", "cage.ogg": "gàbia", "castle.ogg": "castell", "door.ogg": "porta", "fair.ogg": "atracció", "fountain.ogg": "font", "grave.ogg": "tomba", "home.ogg": "casa", "hut.ogg": "cabana", "lane.ogg": "carrils", "pool.ogg": "piscina", "ramp.ogg": "rampa", "roof.ogg": "sostre", "school.ogg": "col·legi", "shed.ogg": "cobert", "shop.ogg": "botiga", "stage.ogg": "escenari", "store.ogg": "botiga", "street.ogg": "carrer", "strawberry.ogg": "maduixa", "bath.ogg": "banyera", "bed.ogg": "llit", "bench.ogg": "banc", "chair.ogg": "cadira", "chest.ogg": "bagul", "couch.ogg": "sofà", "crib.ogg": "bressols", "desk.ogg": "escriptori", "fan.ogg": "ventilador", "lamp.ogg": "el llum", "light.ogg": "la llum", "mat.ogg": "estora", "quilt.ogg": "edredó", "rug.ogg": "catifa", "seat.ogg": "seient", "shelf.ogg": "prestatge", "shower.ogg": "dutxa", "sink.ogg": "pica", "stove.ogg": "cuina", "toilet.ogg": "vàter", "balance.ogg": "balança", "can.ogg": "llaunes", "clock.ogg": "despertador", "dish.ogg": "font", "fork.ogg": "forquilla", "glass.ogg": "got", "knife.ogg": "ganivet", "lid.ogg": "tapa", "mop.ogg": "fregona", "mug.ogg": "tassa", "pan.ogg": "paella", "plate.ogg": "plat", "pot.ogg": "olla", "scale.ogg": "balança", "sponge.ogg": "esponja", "spoon.ogg": "cullera", "trash.ogg": "escombraries", "tray.ogg": "safata", "farm.ogg": "granja", "anchor.ogg": "àncora", "badge.ogg": "insígnia", "bag.ogg": "bossa", "ball.ogg": "bola", "bead.ogg": "bombolla", "block.ogg": "bloc", "board.ogg": "tauló", "bomb.ogg": "bomba", "book.ogg": "llibre", "box.ogg": "caixa", "candle.ogg": "espelma", "cane.ogg": "bastó", "card.ogg": "tarjeta", "cart.ogg": "carro", "cash.ogg": "diners", "chalk.ogg": "guix", "clay.ogg": "argila", "cloth.ogg": "drap", "coin.ogg": "monedes", "comb.ogg": "pinta", "cone.ogg": "con", "crown.ogg": "corona", "cube.ogg": "cub", "drum.ogg": "tambor", "flag.ogg": "bandera", "flute.ogg": "flauta", "game.ogg": "joc", "gift.ogg": "regal", "glue.ogg": "cola", "hook.ogg": "ham", "hose.ogg": "mànega", "ink.ogg": "tinta", "jewel.ogg": "joia", "kite.ogg": "estel", "knot.ogg": "nus", "log.ogg": "troncs", "map.ogg": "mapa", "mask.ogg": "màscara", "match.ogg": "llumins", "nest.ogg": "niu", "net.ogg": "xarxa", "oar.ogg": "rems", "page.ogg": "pàgina", "pair.ogg": "parell", "pen.ogg": "bolígraf", "pencil.ogg": "llapis", "picture.ogg": "pintura", "pole.ogg": "pal", "prize.ogg": "trofeu", "rag.ogg": "drap", "rope.ogg": "corda", "sign.ogg": "el senyal", "sleigh.ogg": "trineu", "slide.ogg": "tobogan", "squirt.ogg": "pulveritzador", "stack.ogg": "apilar", "stamp.ogg": "segell", "straw.ogg": "palles", "string.ogg": "cordill", "tag.ogg": "etiqueta", "thread.ogg": "bobina", "torch.ogg": "torxa", "towel.ogg": "tovallola", "toy.ogg": "joguina", "trap.ogg": "parany", "tube.ogg": "llapis de llavis", "watch.ogg": "rellotge", "wheel.ogg": "roda", "wig.ogg": "perruca", "wood.ogg": "fusta", "link.ogg": "enllaç", "dirty.ogg": "brut", "throw.ogg": "llançar", "brick.ogg": "maons", "brush.ogg": "brotxa", "flash.ogg": "llanterna", "rake.ogg": "rasclet", "screw.ogg": "vis", "spear.ogg": "la llança", "tool.ogg": "eines", "wedge.ogg": "falca", "wrench.ogg": "clau anglesa", "bike.ogg": "bicicleta", "boat.ogg": "barca", "canoe.ogg": "canoa", "car.ogg": "cotxe", "plane.ogg": "avió", "sail.ogg": "veler", "ship.ogg": "vaixell", "sled.ogg": "trineu", "train.ogg": "tren", "truck.ogg": "camió", "black.ogg": "negre", "blue.ogg": "blau", "brown.ogg": "marró", "color.ogg": "colors", "gray.ogg": "gris", "green.ogg": "verd", "orange-color.ogg": "taronja", "pink.ogg": "rosa", "red.ogg": "vermell", "white.ogg": "blanc", "five.ogg": "cinc", "one.ogg": "un", "ten.ogg": "deu", "three.ogg": "tres", "two.ogg": "dos", "van.ogg": "furgoneta", "big.ogg": "gran", "blind.ogg": "cec", "blond.ogg": "rossa", "bright.ogg": "brillant", "crazy.ogg": "ximple", "cute.ogg": "bufona", "dot.ogg": "punt", "empty.ogg": "buida", "fat.ogg": "gras", "flat.ogg": "pisos", "front.ogg": "davant", "full.ogg": "ple", "giant.ogg": "gegant", "happy.ogg": "feliç", "high.ogg": "a gran altura", "hot.ogg": "calent", "huge.ogg": "enorme", "left.ogg": "esquerra", "line.ogg": "codi de barres", "liquid.ogg": "líquid", "magic.ogg": "mag", "on.ogg": "sobre", "pretty.ogg": "bella", "right.ogg": "dreta", "round.ogg": "circular", "royal.ogg": "reial", "shut.ogg": "tancat", "sick.ogg": "malalt", "small.ogg": "petit", "spot.ogg": "taca", "square.ogg": "quadrat", "stand.ogg": "dret", "straight.ogg": "recte", "stripe.ogg": "ratlles", "strong.ogg": "fortalesa", "tall.ogg": "alt", "thick.ogg": "gruixut", "tired.ogg": "cansat", "under.ogg": "sota", "wet.ogg": "mullat", "wheat.ogg": "blat", "U0030.ogg": "zero", "U0031.ogg": "un", "U0032.ogg": "dos", "U0033.ogg": "tres", "U0034.ogg": "quatre", "U0035.ogg": "cinc", "U0036.ogg": "sis", "U0037.ogg": "set", "U0038.ogg": "vuit", "U0039.ogg": "nou", "10.ogg": "deu" } diff --git a/src/activities/imageid/resource/content-de.json b/src/activities/lang/resource/content-de.json similarity index 99% rename from src/activities/imageid/resource/content-de.json rename to src/activities/lang/resource/content-de.json index e19ed5fb0..731d63ae3 100644 --- a/src/activities/imageid/resource/content-de.json +++ b/src/activities/lang/resource/content-de.json @@ -1,1096 +1,1097 @@ { "accountant.ogg": "der Buchhalter", "ache.ogg": "der Schmerz", "acorn.ogg": "die Eichel", "actor.ogg": "der Schauspieler", "air_horn.ogg": "die Hupe", "alarmclock.ogg": "der Wecker", "alligator.ogg": "der Alligator", "alphabet.ogg": "das Alphabet", "anchor.ogg": "der Anker", "angel.ogg": "der Engle", "angry.ogg": "wütend", "animal.ogg": "die Tiere", "ankle.ogg": "der Knöchel", "ant.ogg": "die Ameise", "anteater.ogg": "der Ameisenbär", "antelope.ogg": "die Antilope", "apple.ogg": "der Apfel", "apple_tree.ogg": "der Apfelbaum", "appliance.ogg": "das Gerät", "apricot.ogg": "die Aprikose", "arm.ogg": "der Arm", "armchair.ogg": "der Sessel", "artichoke.ogg": "die Artischocke", "artist.ogg": "der Künstler", "asparagus.ogg": "der Spargel", "astronaut.ogg": "der Astronaut", "athlete.ogg": "der Athlet", "avocado.ogg": "die Avocado", "ax.ogg": "die Axt", "baby_bottle.ogg": "die Babyflasche", "back.ogg": "der Rücken", "badge.ogg": "das Abzeichen", "bag.ogg": "der Rucksack", "bait.ogg": "der Köder", "bake.ogg": "kochen", "balance.ogg": "die Waage", "bald.ogg": "glatzköpfig", "ball.ogg": "der Ball", "ball_of_yarn.ogg": "der Wollknäuel", "ball_soccer.ogg": "der Fußball", "ballet.ogg": "das Ballet", "bank.ogg": "die Bank", "banker_female.ogg": "die Bankangestellte", "bark.ogg": "bellen", "barn.ogg": "die Scheune", "bat.ogg": "die Fledermaus", "bath.ogg": "das Bad", "bathing_suit.ogg": "der Badeanzug", "bay.ogg": "die Bucht", "beach.ogg": "der Strand", "bead.ogg": "die Blase", "bean.ogg": "die Bohne", "bear.ogg": "der Bär", "beard.ogg": "der Bart", "beat.ogg": "schlagen", "beaver.ogg": "der Biber", "bed.ogg": "das Bett", "bedroom.ogg": "das Schlafzimmer", "bee.ogg": "die Biene", "beef.ogg": "das Rindfleisch", "beetle.ogg": "der Käfer", "beg.ogg": "betteln", "behind.ogg": "dahinter", "bell.ogg": "die Glocke", "belly.ogg": "der Bauch", "bench.ogg": "die Bank", "bib.ogg": "der Latz", "big.ogg": "groß", "big_top.ogg": "das Zirkuszelt", "bike.ogg": "das Fahrrad", "bird.ogg": "der Vogel", "bit.ogg": "der Bissen", "bite.ogg": "abbeißen", "black.ogg": "schwarz", "blackberry.ogg": "die Brombeere", "blackbird.ogg": "die Amsel", "blade.ogg": "die Klinge", "blind.ogg": "blind", "blink.ogg": "blinzeln", "block.ogg": "der Block", "blond.ogg": "blond", "blue.ogg": "blau", "blueberry.ogg": "die Heidelbeere", "blush.ogg": "erröten", "board.ogg": "das Brett", "boat.ogg": "das Boot", "boil.ogg": "kochen", "bolt.ogg": "die Mutter", "bomb.ogg": "die Bombe", "bone.ogg": "der Knochen", "book.ogg": "das Buch", "bookcase.ogg": "das Bücherregal", "bottom.ogg": "das Gesäß", "box.ogg": "die Schachtel", "boxer.ogg": "der Boxer", "boy.ogg": "der Junge", "braid.ogg": "der Zopf", "brain.ogg": "das Gehirn", "branch.ogg": "der Ast", "bread.ogg": "das Brot", "break.ogg": "brechen", "breast.ogg": "die Brust", "brick.ogg": "der Ziegelstein", "bricklayer.ogg": "der Maurer", "bride.ogg": "die Braut", "bridge.ogg": "die Brücke", "bright.ogg": "leuchtend", "broccoli.ogg": "der Brokkoli", "brother.ogg": "der Bruder", "brown.ogg": "braun", "brush.ogg": "der Pinsel", "bubble.ogg": "die Blase", "bucket.ogg": "der Eimer", "bud.ogg": "die Knospe", "buffalo.ogg": "der Büffel", "bug.ogg": "das Insekt", "bulb.ogg": "die Glühbirne", "bull.ogg": "der Stier", "bump.ogg": "die Beule", "bun.ogg": "das Brötchen", "bus.ogg": "der Bus", "bush.ogg": "der Busch", "butcher.ogg": "der Metzger", "butter.ogg": "die Butter", "butterfly.ogg": "der Schmetterling", "button.ogg": "der Knopf", "cabbage.ogg": "der Kohl", "cabin.ogg": "die Hütte", "cacao.ogg": "der Kakao", "cactus.ogg": "der Kaktus", "cage.ogg": "der Käfig", "cake.ogg": "die Torte", "call.ogg": "anrufen", "camel.ogg": "das Kamel", "camera.ogg": "der Fotoapparat", "camp.ogg": "zelten", "can.ogg": "die Dose", "canary.ogg": "der Kanarienvogel", "candle.ogg": "die Kerze", "candy.ogg": "das Bonbon", "cane.ogg": "der Stock", "canoe.ogg": "das Kanu", "canon.ogg": "die Kanone", "canyon.ogg": "der Canyon", "cap.ogg": "die Mütze", "cape.ogg": "der Umhang", "car.ogg": "das Auto", "carafe.ogg": "die Karaffe", "card.ogg": "die Karte", "carnival.ogg": "der Karneval", "carpenter.ogg": "der Zimmermann", "carpet.ogg": "der Teppich", "carrot.ogg": "die Karotte", "cart.ogg": "der Einkaufswagen", "cash.ogg": "das Geld", "castle.ogg": "das Schloß", "cat.ogg": "der Kater", "cat_female.ogg": "die Katze", "catch.ogg": "fangen", "caterpillar.ogg": "die Raupe", "cauldron.ogg": "der Kessel", "cauliflower.ogg": "der Blumenkohl", "cavern.ogg": "die Höhle", "celery.ogg": "der Sellerie", "centipede.ogg": "der Tausendfüßler", "cereal.ogg": "das Getreide", "chain.ogg": "die Kette", "chair.ogg": "der Stuhl", "chalk.ogg": "die Kreide", "chameleon.ogg": "das Chamäleon", "chandelier.ogg": "der Kronleuchter", "chat.ogg": "paludern", "cheek.ogg": "die Wange", "cheer.ogg": "jubeln", "cheese.ogg": "der Käse", "chef.ogg": "die Köchin", "cherry.ogg": "die Kirsche", "chest.ogg": "die Truhe", "chick.ogg": "das Küken", "chicken.ogg": "das Huhn", "chief.ogg": "der Indianer", "child.ogg": "das Kind", "chimney.ogg": "der Schornstein", "chimp.ogg": "der Schimpanse", "chin.ogg": "das Kinn", "chocolate.ogg": "die Schokolade", "chop.ogg": "zerschneiden", "chores.ogg": "die Hausarbeit", "christmas.ogg": "das Weihnachten", "cigar.ogg": "die Zigarre", "circus.ogg": "der Zirkus", "city.ogg": "die Stadt", "clam.ogg": "die Muschel", "clap.ogg": "klatschen", "class.ogg": "die Klasse", "claw.ogg": "die Tatze", "clay.ogg": "der Ton", "clean.ogg": "sich waschen", "cleaning_lady.ogg": "die Putzfrau", "cliff.ogg": "das Kliff", "climb.ogg": "klettern", "clock.ogg": "der Wecker", "cloth.ogg": "der Stoff", "clothes_hanger.ogg": "der Kleiderbügel", "cloud.ogg": "die Wolke", "cloudy.ogg": "wolkig", "clover.ogg": "der Klee", "cowboy.ogg": "der Cowboy", "clown.ogg": "der Clown", "coach.ogg": "die Trainerin", "coast.ogg": "die Küste", "coat.ogg": "der Mantel", "cobra.ogg": "die Kobra", "coconut.ogg": "die Kokosnuss", "cod.ogg": "der Dorsch", "coffee.ogg": "der Kaffee", "coin.ogg": "das Geldstück", "cold.ogg": "die Kälte", "color.ogg": "bunt", "colt.ogg": "das Fohlen", "comb.ogg": "der Kamm", "cone.ogg": "der Kegel", "cookie.ogg": "der Keks", "cork.ogg": "der Korken", "corn.ogg": "der Mais", "couch.ogg": "das Sofa", "cough.ogg": "husten", "couple.ogg": "das Paar", "cow.ogg": "die Kuh", "crab.ogg": "der Krebs", "cradle.ogg": "die Wiege", "craft.ogg": "das Handwerk", "crawl.ogg": "krabbeln", "crazy.ogg": "verrückt", "creek.ogg": "der Bach", "crepe.ogg": "der Pfannkuchen", "crib.ogg": "das Kinderbett", "criminal.ogg": "der Verbrecher", "croak.ogg": "quaken", "crocodile.ogg": "das Krokodil", "cross.ogg": "das Kreuz", "crow.ogg": "die Krähe", "crown.ogg": "die Krone", "crumb.ogg": "der Krümel", "crust.ogg": "die Kruste", "cry.ogg": "weinen", "crystal.ogg": "der Kristall", "cube.ogg": "der Würfel", "cucumber.ogg": "die Gurke", "curtain.ogg": "der Vorhang", "cut.ogg": "schneiden", "cute.ogg": "reizend", "dad.ogg": "der Vater", "daffodil.ogg": "die Osterglocke", "daisy.ogg": "die Margerite", "dam.ogg": "der Staudamm ", "dance.ogg": "tanzen", "dandelion.ogg": "der Löwenzahn", "danger.ogg": "die Gefahr", "dart_board.ogg": "die Zielscheibe", "date_fruit.ogg": "die Dattel", "deer.ogg": "der Hirsch", "den.ogg": "der Bau", "desert.ogg": "die Wüste", "desk.ogg": "der Schreibtisch", "dessert.ogg": "der Nachtisch", "diamond.ogg": "der Diamant", "dig.ogg": "graben", "dirt.ogg": "der Schmutz", "dirty.ogg": "schmutzig", "dish.ogg": "das Gericht", "dishcloth.ogg": "der Spüllappen", "dive.ogg": "tauchen", "doctor.ogg": "die Ärztin", "doe.ogg": "die Hirschkuh", "dog.ogg": "der Hund", "doll.ogg": "die Puppe", "dolphin.ogg": "der Delphin", "domino.ogg": "der Domino", "door.ogg": "die Tür", "doormat.ogg": "die Fußmatte", "dot.ogg": "der Punkt", "doughnut.ogg": "der Donut", "dove.ogg": "die Taube", "dragon.ogg": "der Drache", "dragonfly.ogg": "die Libelle", "drank.ogg": "trinken", "draw.ogg": "die Zeichnung", "drawer.ogg": "die Schublade", "dream.ogg": "träumen", "dress.ogg": "das Kleid", "drink.ogg": "das Getränk", "drinking.ogg": "trinken", "drip.ogg": "der Tropfen", "drive.ogg": "fahren", "drool.ogg": "sabbern", "drum.ogg": "die Trommel", "dry.ogg": "trocknen", "duck.ogg": "der Erpel", "duck_mother.ogg": "die Ente", "dune.ogg": "die Düne", "dwarf.ogg": "der Zwerg", "eagle.ogg": "der Adler", "ear.ogg": "das Ohr", "earth.ogg": "die Erde", "eat.ogg": "essen", "egg.ogg": "das Ei", "eggplant.ogg": "die Aubergine", "elbow.ogg": "der Ellbogen", "electrician.ogg": "der Elektriker", "elk.ogg": "der Elch", "empty.ogg": "leer", "engine.ogg": "der Motor", "engineer.ogg": "der Ingenieur", "eraser.ogg": "der Radiergummi", "explore.ogg": "die Erforscher", "eyelash.ogg": "die Wimper", "eyes.ogg": "die Augen", "face.ogg": "das Gesicht", "fair.ogg": "die Kirmes", "fairy.ogg": "die Fee", "fall_down.ogg": "fallen", "fall_season.ogg": "der Herbst", "family.ogg": "die Familie", "fan.ogg": "der Ventilator", "farm.ogg": "der Bauernhof", "farmer.ogg": "der Bauer", "fat.ogg": "dick", "faucet.ogg": "der Wasserhahn", "fawn.ogg": "das Rehkitz", "feast.ogg": "das Hähnchen", "feather.ogg": "die Feder", "feed.ogg": "füttern", "feet.ogg": "Füße", "fell.ogg": "fallen", "femur.ogg": "der Oberschenkelknochen", "fetch.ogg": "bringen", "fig.ogg": "die Feige", "fin.ogg": "die Flosse", "find.ogg": "finden", "finger.ogg": "der Finger", "fire.ogg": "das Feuer", "fire_extinguisher.ogg": "der Feuerlöscher", "fireman.ogg": "der Feuerwehrmann", "fish.ogg": "der Fisch", "fisherman.ogg": "der Fischer", "fist.ogg": "die Faust", "flacon.ogg": "der Flakon", "flag.ogg": "die Flagge", "flame.ogg": "die Flamme", "flamingo.ogg": "der Flamingo", "flash.ogg": "die Taschenlampe", "flat.ogg": "die Wohnungen", "flies.ogg": "die Fliege", "flight.ogg": "der Flug", "float.ogg": "treiben", "flour.ogg": "das Mehl", "flower.ogg": "die Blume", "fluff.ogg": "die Fluse", "flute.ogg": "die Flöte", "fly.ogg": "fliegen", "foam.ogg": "der Schaum", "food.ogg": "der Hamburger", "foot.ogg": "der Fuß", "forest.ogg": "der Wald", "fork.ogg": "die Gabel", "fountain.ogg": "der Brunnen", "fox.ogg": "der Fuchs", "freeze.ogg": "friehren", "friend.ogg": "der Freund", "fries.ogg": "die Pommes Frites", "frog.ogg": "der Frosch", "front.ogg": "davor", "frown.ogg": "murren", "fruit.ogg": "die Früchte", "fudge.ogg": "das Sahnebonbon", "full.ogg": "voll", "fun.ogg": "der Spaß", "fur.ogg": "der Pelz", "game.ogg": "die Freude", "garage.ogg": "die Garage", "garden.ogg": "der Garten", "garlic.ogg": "der Knoblauch", "gem.ogg": "der Edelstein", "giant.ogg": "riesig", "gift.ogg": "das Geschenk", "giraffe.ogg": "die Giraffe", "girl.ogg": "das Mädchen", "glass.ogg": "das Glas", "glasses.ogg": "die Brille", "glove.ogg": "die Handschuhe", "glue.ogg": "der Kleber", "gnome.ogg": "der Gnom", "goat.ogg": "die Ziege", "golden.ogg": "golden", "golf.ogg": "das Golf", "goose.ogg": "die Gans", "gorilla.ogg": "der Gorilla", "grain.ogg": "das Getreide", "grandmother.ogg": "die Großmutter", "grape.ogg": "die Traube", "grapefruit.ogg": "die Pampelmuse", "grass.ogg": "das Gras", "grave.ogg": "das Grab", "gray.ogg": "grau", "green.ogg": "grün", "grill.ogg": "der Grill", "grin.ogg": "lächeln", "ground.ogg": "die Erde", "growl.ogg": "knurren", "guignol.ogg": "der Kasper", "guinea_pig.ogg": "das Meerschweinchen", "gum.ogg": "der Kaugummi", "hair.ogg": "die Haare", "hair_dryer.ogg": "der Föhn", "half.ogg": "halb", "ham.ogg": "der Schinken", "hammer.ogg": "der Hammer", "hand.ogg": "die Hand", "handlebar.ogg": "der Lenker", "happy.ogg": "glücklich", "harp.ogg": "die Harfe", "hat.ogg": "der Hut", "hatch.ogg": "ausschlüpfen", "hay.ogg": "das Heu", "head.ogg": "der Kopf", "hear.ogg": "hören", "heat.ogg": "die Hitze ", "hedge.ogg": "die Hecke", "hedgehog.ogg": "der Igel", "heel.ogg": "der Absatz", "helmet.ogg": "der Helm", "hen.ogg": "die Henne", "herd.ogg": "die Herde", "high.ogg": "hoch", "hike.ogg": "wandern", "hill.ogg": "der Hügel", "hip.ogg": "die Hüfte", "hippopotamus.ogg": "das Nilpferd", "hit.ogg": "schlagen", "hive.ogg": "der Bienenstock", "hockey.ogg": "das Eishockey", "hole.ogg": "das Loch", "home.ogg": "das Zuhause", "hook.ogg": "der Angelhaken", "horse.ogg": "das Pferd", "hose.ogg": "der Schlauch", "hospital.ogg": "das Krankenhaus", "hot.ogg": "heiß", "hot_dog.ogg": "der Hotdog", "hound.ogg": "der Jagdhund", "house.ogg": "das Haus", "howl.ogg": "heulen", "huge.ogg": "riesig", "hummingbird.ogg": "der Kolibri", "hunchback.ogg": "der Buckel", "hunter.ogg": "der Jäger", "husband.ogg": "der Ehemann", "hut.ogg": "die Hütte", "hyena.ogg": "die Hyäne", "ice.ogg": "der Eiswürfel", "iceberg.ogg": "der Eisberg", "iguana.ogg": "der Leguan", "ill.ogg": "krank", "ink.ogg": "die Tinte", "island.ogg": "die Insel", "jacket.ogg": "das Jackett", "jaguar.ogg": "der Jaguar", "jam.ogg": "die Marmelade", "jay.ogg": "der Eichelhäher", "jelly.ogg": "das Gelee", "jellyfish.ogg": "die Qualle", "jewel.ogg": "das Juwel", "job.ogg": "die Arbeit", "jockey.ogg": "der Jockey", "jog.ogg": "das Jogging", "judge.ogg": "der Richter", "judo.ogg": "das Judo", "juggler.ogg": "Jongleur", "juice.ogg": "der Saft", "jump.ogg": "springen", "kangaroo.ogg": "das Känguru", "keel.ogg": "der Kegel", "kernel.ogg": "der Kern", "keyboard.ogg": "die Tastatur", "kimono.ogg": "der Kimono", "king.ogg": "der König", "kiss.ogg": "küssen", "kitchen.ogg": "die Küche", "kite.ogg": "der Drachen", "kitten.ogg": "das Kätzchen", "kiwi.ogg": "die Kiwi", "knee.ogg": "das Knie", "kneel.ogg": "sich hinknien", "knife.ogg": "das Messer", "knight.ogg": "der Ritter", "knit.ogg": "stricken", "knot.ogg": "der Knoten", "koala.ogg": "der Koala", "lady.ogg": "die Dame", "ladybug.ogg": "der Marienkäfer", "lake.ogg": "der See", "lama.ogg": "das Lama", "lamb.ogg": "das Lamm", "lamp.ogg": "die Lampe", "lane.ogg": "die Fahrbahn", "lap.ogg": "der Schoß", "land.ogg": "das Gelände", "lasso.ogg": "das Lasso", "laugh.ogg": "lachen", "lava.ogg": "die Lava", "lawn.ogg": "der Rasen", "lawyer.ogg": "der Rechtsanwalt", "leaf.ogg": "das Blatt", "ledge.ogg": "der Felsvorsprung", "leek.ogg": "der Lauch", "left.ogg": "links", "leg.ogg": "das Bein", "leg_animal.ogg": "das Käferbein", "lemon.ogg": "die Zitrone", "lemonade.ogg": "die Limonade", "lemur.ogg": "der Lemur", "leopard.ogg": "der Leopard", "lettuce.ogg": "der Blattsalat", "librarian.ogg": "die Bibliothekarin", "lick.ogg": "ablecken", "lid.ogg": "der Deckel", "lift.ogg": "hochheben", "light.ogg": "das Licht", "lighthouse.ogg": "der Leuchtturm", "lightning.ogg": "der Blitz", "lilac.ogg": "der Flieder", "lime.ogg": "die Limone", "line.ogg": "der Strich", "link.ogg": "das Kettenglied", "lion.ogg": "der Löwe", "lion_cub.ogg": "das Löwenjunge", "lip.ogg": "die Lippe", "liquid.ogg": "flüssig", "lizard.ogg": "die Eidechse", "lobster.ogg": "der Hummer", "log.ogg": "das Holzscheit", "look.ogg": "das Aussehen", "lunch.ogg": "der Imbiß", "mad.ogg": "der Zorn", "magic.ogg": "magisch", "magnet.ogg": "der Magnet", "magnifying_glass.ogg": "die Lupe", "magpie.ogg": "die Elster", "mail.ogg": "der Briefumschlag", "man.ogg": "der Mann", "mane.ogg": "die Mähne", "mango.ogg": "die Mango", "map.ogg": "die Landkarte", "maple.ogg": "der Ahorn", "marble.ogg": "die Murmel", "mashed_potatoes.ogg": "der Kartoffelbrei", "mask.ogg": "die Taucherbrille", "mast.ogg": "der Mast", "mat.ogg": "die Matte", "match.ogg": "die Streichhölzer", "mate_male.ogg": "der Freund", "mate_female.ogg": "die Freundin", "mattress.ogg": "die Matratze", "mauve.ogg": "lila", "meal.ogg": "die Mahlzeit", "meat.ogg": "das Fleisch", "mechanic.ogg": "der Mechaniker", "medal.ogg": "die Medaille", "meet.ogg": "treffen", "melon.ogg": "die Melone", "men.ogg": "die Menschenmenge", "merry-go-round.ogg": "das Karussell", "mice.ogg": "die Mäuse", "microphone.ogg": "das Mikrophon", "milk.ogg": "die Milch", "mill.ogg": "die Windmühle", "mimosa.ogg": "die Mimose", "mirror.ogg": "der Spiegel", "mixer.ogg": "der Mixer", "mole.ogg": "der Maulwurf", "mom.ogg": "die Mama", "moon.ogg": "der Mond", "moose.ogg": "der Elch", "mop.ogg": "der Mopp", "mosque.ogg": "die Moschee", "mosquito.ogg": "die Mücke", "mother.ogg": "die Mutter", "motorcycle.ogg": "das Motorrad", "mountain.ogg": "der Berg", "mouse.ogg": "die Maus", "mouth.ogg": "der Mund", "movie.ogg": "der Film", "mower.ogg": "der Rasenmäher", "mud.ogg": "der Schlamm", "mug.ogg": "die Tasse", "mule.ogg": "der Esel", "muscle.ogg": "der Muskel", "mushroom.ogg": "der Fliegenpilz", "music.ogg": "die Musik", "musician.ogg": "der Musiker", "naked.ogg": "nackt", "nap.ogg": "schlummern", "navel.ogg": "der Bauchnabel", "neck.ogg": "der Nacken", "necklace.ogg": "die Halskette", "needle.ogg": "die Nadel", "nest.ogg": "das Nest", "net.ogg": "der Kescher", "newspaper.ogg": "die Zeitung", "night.ogg": "die Nacht", "nightgown.ogg": "das Nachthemd", "nose.ogg": "die Nase", "nostril.ogg": "das Nasenloch", "notebook.ogg": "das Notizbuch", "number.ogg": "die Zahl", "nun.ogg": "die Nonne", "nurse.ogg": "die Krankenschwester", "nurse_male.ogg": "der Krankenpfleger", "nut.ogg": "die Erdnuß", "oar.ogg": "das Ruder", "ocean.ogg": "der Ozean", "office.ogg": "das Büro", "olive.ogg": "die Olive", "on.ogg": "darauf", "onion.ogg": "die Zwiebel", "open.ogg": "offen", "opossum.ogg": "das Opossum", "orange-color.ogg": "orange", "orange.ogg": "die Orange", "orchid.ogg": "die Orchidee", "ostrich.ogg": "der Strauß", "otter.ogg": "der Otter", "owl.ogg": "die Eule", "ox.ogg": "der Ochse", "oyster.ogg": "die Auster", "pacifier.ogg": "der Schnuller", "page.ogg": "die Seite", "pair.ogg": "das Paar", "pajamas.ogg": "der Pyjama", "palm_tree.ogg": "die Palme", "pan.ogg": "die Pfanne", "panda.ogg": "der Panda", "panther.ogg": "der Panther", "panties.ogg": "die Unterhose", "pants.ogg": "die Hose", "papaya.ogg": "die Papaya", "paper.ogg": "das Papier", "parachute.ogg": "der Fallschirm", "parakeet.ogg": "der Sittich", "parrot.ogg": "der Papagei", "patch.ogg": "der Flicken", "path.ogg": "der Weg", "paw.ogg": "die Pfote", "pea.ogg": "die Erbse", "peach.ogg": "der Pfirsich", "peacock.ogg": "der Pfau", "peak.ogg": "der Gipfel", "pear.ogg": "die Birne", "pearl.ogg": "die Perle", "peck.ogg": "picken", "pedal.ogg": "das Pedal", "pelican.ogg": "der Pelikan", "pen.ogg": "der Füller", "pencil.ogg": "der Bleistift", "peony.ogg": "die Päonie", "people.ogg": "die Leute", "pepper.ogg": "der Pfeffer", "peppers.ogg": "die Peperoni", "petal.ogg": "das Blütenblatt", "petite.ogg": "klein", "phone.ogg": "das Telefon", "piano.ogg": "das Klavier", "picture.ogg": "das Bild", "pie.ogg": "der Kuchen", "pig.ogg": "das Schwein", "pigeon.ogg": "die Taube", "pill.ogg": "die Tablette", "pillow.ogg": "das Kissen", "pilot.ogg": "der Pilot", "pine.ogg": "die Pinie", "pine_cone.ogg": "der Tannenzapfen", "pink.ogg": "rosa", "pip.ogg": "der Kern", "pipe.ogg": "die Pfeife", "piranha.ogg": "der Piranha", "pirate.ogg": "der Pirat", "pizza.ogg": "die Pizza", "plane.ogg": "das Flugzeug", "planet.ogg": "der Planet", "plant.ogg": "die Pflanze", "plate.ogg": "der Teller", "play.ogg": "spielen", "pliers.ogg": "die Zange", "plum.ogg": "die Pflaume", "plow.ogg": "pflügen", "plumber.ogg": "der Klempner", "pocket.ogg": "die Hosentasche", "pod.ogg": "die Schote", "pole.ogg": "der Pfosten", "police.ogg": "der Polizist", "pompon.ogg": "der Pompon", "pond.ogg": "der Teich", "pony.ogg": "das Ponny", "pool.ogg": "das Schwimmbad", "pop.ogg": "das Popcorn", "pope.ogg": "der Papst", "porthole.ogg": "das Bullauge", "post.ogg": "der Briefkasten", "pot.ogg": "der Topf", "potato.ogg": "die Kartoffel", "pounce.ogg": "anspringen", "president.ogg": "der Präsident", "pretty.ogg": "schön", "price.ogg": "der Preis", "priest.ogg": "der Pfarrer", "prince.ogg": "der Prinz", "princess.ogg": "die Prinzession", "prison.ogg": "das Gefängnis", "prisoner.ogg": "der Häftling", "prize.ogg": "der Pokal", "pug.ogg": "der Mops", "pull.ogg": "ziehen", "pullover.ogg": "der Pullover", "pumpkin.ogg": "der Kürbis", "puppy.ogg": "der Welpe", "pyramid.ogg": "die Pyramide", "quarrel.ogg": "sich streiten", "queen.ogg": "die Königin", "question.ogg": "die Frage", "quilt.ogg": "die Steppdecke", "quiz.ogg": "das Quiz", "rabbit.ogg": "das Kaninchen", "rabbit_baby.ogg": "das Kaninchenjunge", "race.ogg": "das Wettrennen", "radio.ogg": "das Radio", "radish.ogg": "der Rettich", "raft.ogg": "das Floß", "rag.ogg": "der Lappen", "rage.ogg": "die Wut", "rain.ogg": "der Regen", "raincoat.ogg": "der Regenmantel", "rake.ogg": "die Harke", "ramp.ogg": "die Rampe", "raspberry.ogg": "die Himbeere", "rat.ogg": "die Ratte", "razor.ogg": "der Rasierer", "read.ogg": "lesen", "red.ogg": "rot", "reptile.ogg": "das Reptil", "rhinoceros.ogg": "das Nashorn", "rice.ogg": "der Reis", "ride.ogg": "Fahrrad fahren", "rifle.ogg": "das Gewehr", "right.ogg": "rechts", "rip.ogg": "das Grab", "rise.ogg": "ansteigen", "river.ogg": "der Fluß", "road.ogg": "die Straße", "roast.ogg": "der Braten", "robe.ogg": "die Robe", "robot.ogg": "der Roboter", "rock.ogg": "der Stein", "rocket.ogg": "die Rakete", "rolling_pin.ogg": "das Nudelholz", "roof.ogg": "das Dach", "room.ogg": "das Zimmer", "root.ogg": "die Wurzel", "rope.ogg": "das Seil", "rose.ogg": "die Rose", "round.ogg": "rund", "rowing.ogg": "das Rudern", "royal.ogg": "königlich", "rug.ogg": "der Vorleger", "run.ogg": "rennen", "sad.ogg": "traurig", "saddle.ogg": "der Sattel", "sail.ogg": "das Segelboot", "sailor.ogg": "der Matrose", "salamander.ogg": "der Salamander", "salmon.ogg": "der Lachs", "sand.ogg": "der Sand", "sandals.ogg": "die Sandalen", "sandwich.ogg": "das Sandwich", "sash.ogg": "die Schärpe", "sat.ogg": "sich hinsetzen", "sauce.ogg": "die Sauce", "sausage.ogg": "die Wurst", "scale.ogg": "die Waage", "scar.ogg": "die Narbe", "scared.ogg": "erschrecken", "scarf.ogg": "der Schal", "school.ogg": "die Schule", "school_bag.ogg": "die Schultasche", "science.ogg": "die Wissenschaft", "scissors.ogg": "die Schere", "scorpion.ogg": "der Skorpion", "scratch.ogg": "kratzen", "scream.ogg": "schreien", "screw.ogg": "die Schraube", "screwdriver.ogg": "der Schraubenzieher", "scribble.ogg": "kritzeln", "sea.ogg": "das Meer", "seat.ogg": "der Stuhl", "see.ogg": "sehen", "seed.ogg": "der Samen", "shadow.ogg": "der Schatten", "shake.ogg": "schütteln", "shark.ogg": "der Hai", "shave.ogg": "scheren", "shed.ogg": "der Schuppen", "sheep.ogg": "das Schaf", "shelf.ogg": "das Regal", "shell.ogg": "die Muschel", "ship.ogg": "das Schiff", "shirt.ogg": "das Hemd", "shoe.ogg": "der Schuh", "shoelace.ogg": "der Schnürsenkel", "shop.ogg": "der Laden", "shore.ogg": "das Ufer", "short.ogg": "die Shorts", "shovel.ogg": "die Schippe", "shower.ogg": "die Dusche", "shrimp.ogg": "die Garnele", "shrub.ogg": "der Strauch", "shut.ogg": "geschlossen", "shutter.ogg": "der Fensterladen", "sick.ogg": "krank", "sidewalk.ogg": "der Gehweg", "sign.ogg": "das Schild", "sing.ogg": "singen", "sink.ogg": "die Spüle", "sip.ogg": "nippen", "sister.ogg": "die Schwester", "sit.ogg": "sich hinsetzen", "skate.ogg": "das Skateboardfahren", "skeleton.ogg": "das Skelett", "ski.ogg": "das Skifahren", "skimmer.ogg": "der Schaumlöffel", "skin.ogg": "die Haut", "skirt.ogg": "der Rock", "skunk.ogg": "das Stinktier", "sky.ogg": "der Himmel", "sled.ogg": "der Schlitten", "sleep.ogg": "schlafen", "sleeve.ogg": "der Ärmel", "sleigh.ogg": "der Pferdeschlitten", "slide.ogg": "die Rutsche", "slim.ogg": "schlank", "slime.ogg": "der Schleim", "slippers.ogg": "der Hausschuh", "slope.ogg": "der Abhang", "sloppy.ogg": "durchnäßt", "slot.ogg": "der Schlitz", "sloth.ogg": "das Faultier", "slug.ogg": "die Nacktschnecke", "small.ogg": "klein", "smell.ogg": "riechen", "smile.ogg": "lächeln", "smock.ogg": "der Kittel", "smoke.ogg": "der Rauch", "smooch.ogg": "küssen", "snack.ogg": "der Imbiss", "snail.ogg": "die Schnecke", "snake.ogg": "die Schlange", "sneaker.ogg": "der Turnschuh", "sniff.ogg": "schnüffeln", "snow.ogg": "der Schnee", "soap.ogg": "die Seife", "sob.ogg": "schluchzen", "sock.ogg": "die Socke", "soldier.ogg": "die Soldatin", "sole.ogg": "die Seezunge", "sole_shoe.ogg": "die Schuhsohle", "son.ogg": "der Sohn", "soup.ogg": "die Suppe", "spade.ogg": "der Spaten", "spaghetti.ogg": "die Spaghetti", "spark.ogg": "der Funke", "sparrow.ogg": "der Spatz", "spatula.ogg": "der Pfannenwender", "speak.ogg": "sprechen", "spear.ogg": "der Speer", "spice.ogg": "das Gewürz", "spider.ogg": "die Spinne", "spider_web.ogg": "das Spinnennetz", "spike.ogg": "der Stachel", "spill.ogg": "ausschütten", "spinach.ogg": "der Spinat", "spine.ogg": "die Wirbelsäule", "spinning_top.ogg": "der Kreisel", "splash.ogg": "spritzen", "splatter.ogg": "der Spritzer", "sponge.ogg": "der Schwamm", "spool.ogg": "die Spule", "spoon.ogg": "der Löffel", "sport.ogg": "der Sport", "spot.ogg": "fleckig", "spray.ogg": "sprühen", "spread.ogg": "verteilen", "spring.ogg": "federn", "spring_season.ogg": "der Frühling", "sprinkle.ogg": "der Streusel", "square.ogg": "das Quadrat", "squash.ogg": "der Kürbis", "squat.ogg": "die Hocke", "squid.ogg": "der Tintenfisch", "squirrel.ogg": "das Eichhörnchen", "stack.ogg": "der Stapel", "stage.ogg": "die Bühne", "staircase.ogg": "die Treppe", "stamp.ogg": "die Briefmarke", "stand.ogg": "stehend", "star.ogg": "der Stern", "stare.ogg": "der Blick", "starfish.ogg": "der Seestern", "steak.ogg": "das Steak", "steam.ogg": "der Dampf", "steep.ogg": "steil", "steeple.ogg": "der Kirchturm", "stem.ogg": "der Stiel", "step.ogg": "die Stufe", "stew.ogg": "der Eintopf", "stick.ogg": "der Ast", "sting.ogg": "stechen", "stinky.ogg": "stinkend", "stitch.ogg": "nähen", "stomach.ogg": "der Magen", "stone.ogg": "der Stein", "stop.ogg": "anhalten", "store.ogg": "das Geschäft", "stove.ogg": "der Herd", "straight.ogg": "gerade", "strainer.ogg": "das Sieb", "straw.ogg": "der Strohhalm", "strawberry.ogg": "die Erdbeere", "stream.ogg": "der Strom", "street.ogg": "die Straße", "stretch.ogg": "sich strecken", "string.ogg": "die Schnur", "stripe.ogg": "gestreift", "strong.ogg": "stark", "student.ogg": "der Student", "study.ogg": "lernen", "stump.ogg": "der Baumstumpf", "sugar.ogg": "der Zucker", "suit.ogg": "der Anzug", "suitcase.ogg": "der Koffer", "summer.ogg": "der Sommer", "summit.ogg": "der Gipfel", "sun.ogg": "die Sonne", "swan.ogg": "der Schwan", "sweat.ogg": "schwitzen", "sweatshirt.ogg": "das Sweatshirt", "swim.ogg": "schwimmen", "table.ogg": "der Tisch", "tablecloth.ogg": "die Tischdecke", "tadpole.ogg": "die Kaulquappe", "tag.ogg": "das Etikett", "tail.ogg": "der Schwanz", "tall.ogg": "groß", "tape_measure.ogg": "das Metermaß", "taxi.ogg": "das Taxi", "teach.ogg": "unterrichten", "teacher.ogg": "die Lehrerin", "tear.ogg": "zerreißen", "teddy.ogg": "der Teddybär", "teeth.ogg": "die Zähne", "television.ogg": "das Fernsehen", "temple.ogg": "der Tempel", "tennis.ogg": "das Tennis", "tent.ogg": "das Zelt", "text.ogg": "der Text", "thick.ogg": "dick", "thief.ogg": "der Dieb", "thigh.ogg": "der Oberschenkel", "think.ogg": "denken", "thread.ogg": "die Spule", "throat.ogg": "der Rachen", "throw.ogg": "werfen", "thumb.ogg": "der Daumen", "ticket.ogg": "die Eintrittskarte", "tiger.ogg": "der Tiger", "time.ogg": "die Zeit", "tin.ogg": "die Dose", "tire.ogg": "der Reifen", "tired.ogg": "müde", "tissue.ogg": "das Papiertaschentuch", "toad.ogg": "die Kröte", "toaster.ogg": "der Toaster", "toe.ogg": "der Zeh", "toilet.ogg": "die Toilette", "tomatoe.ogg": "die Tomate", "tongs.ogg": "die Zange", "tongue.ogg": "die Zunge", "tool.ogg": "das Werkzeug", "top.ogg": "darauf", "torch.ogg": "die Fackel", "touch.ogg": "berühren", "towel.ogg": "das Handtuch", "toy.ogg": "das Spielzeug", "trail.ogg": "der Pfad", "train.ogg": "der Zug", "train_station.ogg": "der Bahnhof", "trap.ogg": "die Falle", "trash.ogg": "der Schmutz", "tray.ogg": "das Tablett", "treat.ogg": "die Süßigkeit", "tree.ogg": "der Baum", "triangle.ogg": "dreieckig", "tribe.ogg": "der Stamm", "trip.ogg": "die Reise", "truck.ogg": "der Lastwagen", "tube.ogg": "der Lippenstift", "tulip.ogg": "die Tulpe", "tune.ogg": "die Melodie", "turkey.ogg": "der Truthahn", "turnip.ogg": "die Rübe", "turtle.ogg": "die Schildkröte", "tusk.ogg": "der Stoßzahn", "twin_boys.ogg": "der Zwillingsbruder", "twin_girls.ogg": "die Zwillingsschwester", "umbrella.ogg": "der Regenschirm", "under.ogg": "darunter", "uniform.ogg": "die Uniform", "van.ogg": "der Kleinbus", "vase.ogg": "die Vase", "vegetable.ogg": "das Gemüse", "vein.ogg": "die Ader", "verdure.ogg": "das Grün", "vest.ogg": "die Veste", "vet.ogg": "der Tierarzt", "viper.ogg": "die Viper", "vowel.ogg": "der Vokal", "vulture.ogg": "der Geier", "wag.ogg": "schwänzeln", "walk.ogg": "gehen", "wall.ogg": "die Mauer", "walnut.ogg": "die Walnuß", "wart.ogg": "die Warze", "wash.ogg": "baden", "wasp.ogg": "die Wespe", "watch.ogg": "die Armbanduhr", "water.ogg": "das Wasser", "wave.ogg": "die Welle", "wedding.ogg": "die Hochzeit", "wedge.ogg": "der Keil", "weight.ogg": "das Gewicht", "wet.ogg": "naß", "whale.ogg": "der Wal", "wheat.ogg": "der Weizen", "wheel.ogg": "das Rad", "whisk.ogg": "der Schneebesen", "whisper.ogg": "flüstern", "white.ogg": "weiß", "wide.ogg": "breit", "wife.ogg": "die Ehefrau", "wig.ogg": "die Perücke", "win.ogg": "gewinnen", "wind.ogg": "der Wind", "window.ogg": "das Fenster", "window_glass.ogg": "die Fensterscheibe", "wing.ogg": "der Flügel", "winter.ogg": "der Winter", "wolf.ogg": "der Wolf", "woman.ogg": "die Frau", "wood.ogg": "das Holz", "word.ogg": "das Wort", "worker.ogg": "der Arbeiter", "world.ogg": "die Welt", "wreath.ogg": "der Kranz", "wrench.ogg": "der Schraubenschlüssel", "wrist.ogg": "das Handgelenk", "write.ogg": "schreiben", "yellow.ogg": "gelb", "yogurt.ogg": "der Joghurt", "yum.ogg": "lecker", "zebra.ogg": "das Zebra", "zipper.ogg": "der Reißverschluß", "zoo.ogg": "der Zoo", "U0030.ogg": "null", "U0031.ogg": "eins", "U0032.ogg": "zwei", "U0033.ogg": "drei", "U0034.ogg": "vier", "U0035.ogg": "fünf", "U0036.ogg": "sechs", "U0037.ogg": "sieben", "U0038.ogg": "acht", "U0039.ogg": "neun", "10.ogg": "zehn", "11.ogg": "elf", "12.ogg": "zwölf", "16.ogg": "sechzehn" } + diff --git a/src/activities/imageid/resource/content-en.json b/src/activities/lang/resource/content-en.json similarity index 100% rename from src/activities/imageid/resource/content-en.json rename to src/activities/lang/resource/content-en.json diff --git a/src/activities/imageid/resource/content-es.json b/src/activities/lang/resource/content-es.json similarity index 100% rename from src/activities/imageid/resource/content-es.json rename to src/activities/lang/resource/content-es.json diff --git a/src/activities/imageid/resource/content-fr.json b/src/activities/lang/resource/content-fr.json similarity index 100% rename from src/activities/imageid/resource/content-fr.json rename to src/activities/lang/resource/content-fr.json diff --git a/src/activities/imageid/resource/content-gd.json b/src/activities/lang/resource/content-gd.json similarity index 100% rename from src/activities/imageid/resource/content-gd.json rename to src/activities/lang/resource/content-gd.json diff --git a/src/activities/imageid/resource/content-it.json b/src/activities/lang/resource/content-it.json similarity index 100% rename from src/activities/imageid/resource/content-it.json rename to src/activities/lang/resource/content-it.json diff --git a/src/activities/imageid/resource/content-pt_BR.json b/src/activities/lang/resource/content-pt_BR.json similarity index 100% rename from src/activities/imageid/resource/content-pt_BR.json rename to src/activities/lang/resource/content-pt_BR.json diff --git a/src/activities/imageid/resource/datasetToPo.py b/src/activities/lang/resource/datasetToPo.py similarity index 100% rename from src/activities/imageid/resource/datasetToPo.py rename to src/activities/lang/resource/datasetToPo.py diff --git a/src/activities/imageid/resource/imageid-bg.svg b/src/activities/lang/resource/imageid-bg.svg similarity index 100% rename from src/activities/imageid/resource/imageid-bg.svg rename to src/activities/lang/resource/imageid-bg.svg diff --git a/src/activities/imageid/resource/imageid_frame.svg b/src/activities/lang/resource/imageid_frame.svg similarity index 100% rename from src/activities/imageid/resource/imageid_frame.svg rename to src/activities/lang/resource/imageid_frame.svg diff --git a/src/activities/imageid/resource/words.json b/src/activities/lang/resource/words.json similarity index 99% rename from src/activities/imageid/resource/words.json rename to src/activities/lang/resource/words.json index a8d06527b..1925a13c9 100644 --- a/src/activities/imageid/resource/words.json +++ b/src/activities/lang/resource/words.json @@ -1,5700 +1,5700 @@ [ { "type": "chapter", "name": "other", "content": [ { "type": "lesson", "name": "other", "content": [ { "description": "alphabet", "image": "words/alphabet.png", "voice": "voices-$CA/$LOCALE/words/alphabet.$CA" }, { "description": "bit", "image": "words/bit.png", "voice": "voices-$CA/$LOCALE/words/bit.$CA" }, { "description": "bubble", "image": "words/bubble.png", "voice": "voices-$CA/$LOCALE/words/bubble.$CA" }, { "description": "carnival", "image": "words/carnival.png", "voice": "voices-$CA/$LOCALE/words/carnival.$CA" }, { "description": "craft", "image": "words/craft.png", "voice": "voices-$CA/$LOCALE/words/craft.$CA" }, { "description": "link", "image": "words/link.png", "voice": "voices-$CA/$LOCALE/words/link.$CA" }, { "description": "number", "image": "words/number.png", "voice": "voices-$CA/$LOCALE/words/number.$CA" }, { "description": "question", "image": "words/question.png", "voice": "voices-$CA/$LOCALE/words/question.$CA" }, { "description": "quiz", "image": "words/quiz.png", "voice": "voices-$CA/$LOCALE/words/quiz.$CA" }, { "description": "science", "image": "words/science.png", "voice": "voices-$CA/$LOCALE/words/science.$CA" }, { "description": "splatter", "image": "words/splatter.png", "voice": "voices-$CA/$LOCALE/words/splatter.$CA" }, { "description": "text", "image": "words/text.png", "voice": "voices-$CA/$LOCALE/words/text.$CA" }, { "description": "tune", "image": "words/tune.png", "voice": "voices-$CA/$LOCALE/words/tune.$CA" }, { "description": "vowel", "image": "words/vowel.png", "voice": "voices-$CA/$LOCALE/words/vowel.$CA" }, { "description": "weight", "image": "words/weight.png", "voice": "voices-$CA/$LOCALE/words/weight.$CA" }, { "description": "word", "image": "words/word.png", "voice": "voices-$CA/$LOCALE/words/word.$CA" } ] }, { "type": "lesson", "name": "action", "content": [ { "description": "ate", "image": "words/ate.png", "voice": "voices-$CA/$LOCALE/words/ate.$CA" }, { "description": "bake", "image": "words/bake.png", "voice": "voices-$CA/$LOCALE/words/bake.$CA" }, { "description": "bark", "image": "words/bark.png", "voice": "voices-$CA/$LOCALE/words/bark.$CA" }, { "description": "beat", "image": "words/beat.png", "voice": "voices-$CA/$LOCALE/words/beat.$CA" }, { "description": "beg", "image": "words/beg.png", "voice": "voices-$CA/$LOCALE/words/beg.$CA" }, { "description": "bite", "image": "words/bite.png", "voice": "voices-$CA/$LOCALE/words/bite.$CA" }, { "description": "blink", "image": "words/blink.png", "voice": "voices-$CA/$LOCALE/words/blink.$CA" }, { "description": "boil", "image": "words/boil.png", "voice": "voices-$CA/$LOCALE/words/boil.$CA" }, { "description": "break", "image": "words/break.png", "voice": "voices-$CA/$LOCALE/words/break.$CA" }, { "description": "call", "image": "words/call.png", "voice": "voices-$CA/$LOCALE/words/call.$CA" }, { "description": "catch", "image": "words/catch.png", "voice": "voices-$CA/$LOCALE/words/catch.$CA" }, { "description": "chat", "image": "words/chat.png", "voice": "voices-$CA/$LOCALE/words/chat.$CA" }, { "description": "chop", "image": "words/chop.png", "voice": "voices-$CA/$LOCALE/words/chop.$CA" }, { "description": "chores", "image": "words/chores.png", "voice": "voices-$CA/$LOCALE/words/chores.$CA" }, { "description": "clap", "image": "words/clap.png", "voice": "voices-$CA/$LOCALE/words/clap.$CA" }, { "description": "clean", "image": "words/clean.png", "voice": "voices-$CA/$LOCALE/words/clean.$CA" }, { "description": "cough", "image": "words/cough.png", "voice": "voices-$CA/$LOCALE/words/cough.$CA" }, { "description": "crawl", "image": "words/crawl.png", "voice": "voices-$CA/$LOCALE/words/crawl.$CA" }, { "description": "croak", "image": "words/croak.png", "voice": "voices-$CA/$LOCALE/words/croak.$CA" }, { "description": "cry", "image": "words/cry.png", "voice": "voices-$CA/$LOCALE/words/cry.$CA" }, { "description": "cut", "image": "words/cut.png", "voice": "voices-$CA/$LOCALE/words/cut.$CA" }, { "description": "dig", "image": "words/dig.png", "voice": "voices-$CA/$LOCALE/words/dig.$CA" }, { "description": "drank", "image": "words/drank.png", "voice": "voices-$CA/$LOCALE/words/drank.$CA" }, { "description": "draw", "image": "words/draw.png", "voice": "voices-$CA/$LOCALE/words/draw.$CA" }, { "description": "dream", "image": "words/dream.png", "voice": "voices-$CA/$LOCALE/words/dream.$CA" }, { "description": "drinking", "image": "words/drinking.png", "voice": "voices-$CA/$LOCALE/words/drinking.$CA" }, { "description": "drive", "image": "words/drive.png", "voice": "voices-$CA/$LOCALE/words/drive.$CA" }, { "description": "drool", "image": "words/drool.png", "voice": "voices-$CA/$LOCALE/words/drool.$CA" }, { "description": "dry", "image": "words/dry.png", "voice": "voices-$CA/$LOCALE/words/dry.$CA" }, { "description": "eat", "image": "words/eat.png", "voice": "voices-$CA/$LOCALE/words/eat.$CA" }, { "description": "fall down", "image": "words/fall_down.png", "voice": "voices-$CA/$LOCALE/words/fall_down.$CA" }, { "description": "feed", "image": "words/feed.png", "voice": "voices-$CA/$LOCALE/words/feed.$CA" }, { "description": "fell", "image": "words/fell.png", "voice": "voices-$CA/$LOCALE/words/fell.$CA" }, { "description": "fetch", "image": "words/fetch.png", "voice": "voices-$CA/$LOCALE/words/fetch.$CA" }, { "description": "find", "image": "words/find.png", "voice": "voices-$CA/$LOCALE/words/find.$CA" }, { "description": "flight", "image": "words/flight.png", "voice": "voices-$CA/$LOCALE/words/flight.$CA" }, { "description": "float", "image": "words/float.png", "voice": "voices-$CA/$LOCALE/words/float.$CA" }, { "description": "fly", "image": "words/fly.png", "voice": "voices-$CA/$LOCALE/words/fly.$CA" }, { "description": "freeze", "image": "words/freeze.png", "voice": "voices-$CA/$LOCALE/words/freeze.$CA" }, { "description": "growl", "image": "words/growl.png", "voice": "voices-$CA/$LOCALE/words/growl.$CA" }, { "description": "hatch", "image": "words/hatch.png", "voice": "voices-$CA/$LOCALE/words/hatch.$CA" }, { "description": "hear", "image": "words/hear.png", "voice": "voices-$CA/$LOCALE/words/hear.$CA" }, { "description": "howl", "image": "words/howl.png", "voice": "voices-$CA/$LOCALE/words/howl.$CA" }, { "description": "hug", "image": "words/hug.png", "voice": "voices-$CA/$LOCALE/words/hug.$CA" }, { "description": "jump", "image": "words/jump.png", "voice": "voices-$CA/$LOCALE/words/jump.$CA" }, { "description": "kiss", "image": "words/kiss.png", "voice": "voices-$CA/$LOCALE/words/kiss.$CA" }, { "description": "kneel", "image": "words/kneel.png", "voice": "voices-$CA/$LOCALE/words/kneel.$CA" }, { "description": "knit", "image": "words/knit.png", "voice": "voices-$CA/$LOCALE/words/knit.$CA" }, { "description": "lick", "image": "words/lick.png", "voice": "voices-$CA/$LOCALE/words/lick.$CA" }, { "description": "look", "image": "words/look.png", "voice": "voices-$CA/$LOCALE/words/look.$CA" }, { "description": "meet", "image": "words/meet.png", "voice": "voices-$CA/$LOCALE/words/meet.$CA" }, { "description": "nap", "image": "words/nap.png", "voice": "voices-$CA/$LOCALE/words/nap.$CA" }, { "description": "peck", "image": "words/peck.png", "voice": "voices-$CA/$LOCALE/words/peck.$CA" }, { "description": "play", "image": "words/play.png", "voice": "voices-$CA/$LOCALE/words/play.$CA" }, { "description": "plow", "image": "words/plow.png", "voice": "voices-$CA/$LOCALE/words/plow.$CA" }, { "description": "pounce", "image": "words/pounce.png", "voice": "voices-$CA/$LOCALE/words/pounce.$CA" }, { "description": "pull", "image": "words/pull.png", "voice": "voices-$CA/$LOCALE/words/pull.$CA" }, { "description": "quarrel", "image": "words/quarrel.png", "voice": "voices-$CA/$LOCALE/words/quarrel.$CA" }, { "description": "read", "image": "words/read.png", "voice": "voices-$CA/$LOCALE/words/read.$CA" }, { "description": "rip", "image": "words/rip.png", "voice": "voices-$CA/$LOCALE/words/rip.$CA" }, { "description": "rise", "image": "words/rise.png", "voice": "voices-$CA/$LOCALE/words/rise.$CA" }, { "description": "run", "image": "words/run.png", "voice": "voices-$CA/$LOCALE/words/run.$CA" }, { "description": "sat", "image": "words/sat.png", "voice": "voices-$CA/$LOCALE/words/sat.$CA" }, { "description": "scared", "image": "words/scared.png", "voice": "voices-$CA/$LOCALE/words/scared.$CA" }, { "description": "scratch", "image": "words/scratch.png", "voice": "voices-$CA/$LOCALE/words/scratch.$CA" }, { "description": "scream", "image": "words/scream.png", "voice": "voices-$CA/$LOCALE/words/scream.$CA" }, { "description": "scribble", "image": "words/scribble.png", "voice": "voices-$CA/$LOCALE/words/scribble.$CA" }, { "description": "see", "image": "words/see.png", "voice": "voices-$CA/$LOCALE/words/see.$CA" }, { "description": "shake", "image": "words/shake.png", "voice": "voices-$CA/$LOCALE/words/shake.$CA" }, { "description": "shave", "image": "words/shave.png", "voice": "voices-$CA/$LOCALE/words/shave.$CA" }, { "description": "sing", "image": "words/sing.png", "voice": "voices-$CA/$LOCALE/words/sing.$CA" }, { "description": "sip", "image": "words/sip.png", "voice": "voices-$CA/$LOCALE/words/sip.$CA" }, { "description": "sit", "image": "words/sit.png", "voice": "voices-$CA/$LOCALE/words/sit.$CA" }, { "description": "slam", "image": "words/slam.png", "voice": "voices-$CA/$LOCALE/words/slam.$CA" }, { "description": "sleep", "image": "words/sleep.png", "voice": "voices-$CA/$LOCALE/words/sleep.$CA" }, { "description": "smell", "image": "words/smell.png", "voice": "voices-$CA/$LOCALE/words/smell.$CA" }, { "description": "smooch", "image": "words/smooch.png", "voice": "voices-$CA/$LOCALE/words/smooch.$CA" }, { "description": "sniff", "image": "words/sniff.png", "voice": "voices-$CA/$LOCALE/words/sniff.$CA" }, { "description": "speak", "image": "words/speak.png", "voice": "voices-$CA/$LOCALE/words/speak.$CA" }, { "description": "spill", "image": "words/spill.png", "voice": "voices-$CA/$LOCALE/words/spill.$CA" }, { "description": "splash", "image": "words/splash.png", "voice": "voices-$CA/$LOCALE/words/splash.$CA" }, { "description": "spray", "image": "words/spray.png", "voice": "voices-$CA/$LOCALE/words/spray.$CA" }, { "description": "spread", "image": "words/spread.png", "voice": "voices-$CA/$LOCALE/words/spread.$CA" }, { "description": "spring", "image": "words/spring.png", "voice": "voices-$CA/$LOCALE/words/spring.$CA" }, { "description": "squat", "image": "words/squat.png", "voice": "voices-$CA/$LOCALE/words/squat.$CA" }, { "description": "sting", "image": "words/sting.png", "voice": "voices-$CA/$LOCALE/words/sting.$CA" }, { "description": "stitch", "image": "words/stitch.png", "voice": "voices-$CA/$LOCALE/words/stitch.$CA" }, { "description": "stop", "image": "words/stop.png", "voice": "voices-$CA/$LOCALE/words/stop.$CA" }, { "description": "stretch", "image": "words/stretch.png", "voice": "voices-$CA/$LOCALE/words/stretch.$CA" }, { "description": "study", "image": "words/study.png", "voice": "voices-$CA/$LOCALE/words/study.$CA" }, { "description": "teach", "image": "words/teach.png", "voice": "voices-$CA/$LOCALE/words/teach.$CA" }, { "description": "tear", "image": "words/tear.png", "voice": "voices-$CA/$LOCALE/words/tear.$CA" }, { "description": "think", "image": "words/think.png", "voice": "voices-$CA/$LOCALE/words/think.$CA" }, { "description": "touch", "image": "words/touch.png", "voice": "voices-$CA/$LOCALE/words/touch.$CA" }, { "description": "wag", "image": "words/wag.png", "voice": "voices-$CA/$LOCALE/words/wag.$CA" }, { "description": "walk", "image": "words/walk.png", "voice": "voices-$CA/$LOCALE/words/walk.$CA" }, { "description": "wash", "image": "words/wash.png", "voice": "voices-$CA/$LOCALE/words/wash.$CA" }, { "description": "whisper", "image": "words/whisper.png", "voice": "voices-$CA/$LOCALE/words/whisper.$CA" }, { "description": "win", "image": "words/win.png", "voice": "voices-$CA/$LOCALE/words/win.$CA" }, { "description": "write", "image": "words/write.png", "voice": "voices-$CA/$LOCALE/words/write.$CA" } ] }, { "type": "lesson", "name": "adjective", "content": [ { "description": "behind", "image": "words/behind.png", "voice": "voices-$CA/$LOCALE/words/behind.$CA" }, { "description": "big", "image": "words/big.png", "voice": "voices-$CA/$LOCALE/words/big.$CA" }, { "description": "blind", "image": "words/blind.png", "voice": "voices-$CA/$LOCALE/words/blind.$CA" }, { "description": "blond", "image": "words/blond.png", "voice": "voices-$CA/$LOCALE/words/blond.$CA" }, { "description": "bright", "image": "words/bright.png", "voice": "voices-$CA/$LOCALE/words/bright.$CA" }, { "description": "crazy", "image": "words/crazy.png", "voice": "voices-$CA/$LOCALE/words/crazy.$CA" }, { "description": "cute", "image": "words/cute.png", "voice": "voices-$CA/$LOCALE/words/cute.$CA" }, { "description": "dirty", "image": "words/dirty.png", "voice": "voices-$CA/$LOCALE/words/dirty.$CA" }, { "description": "empty", "image": "words/empty.png", "voice": "voices-$CA/$LOCALE/words/empty.$CA" }, { "description": "fat", "image": "words/fat.png", "voice": "voices-$CA/$LOCALE/words/fat.$CA" }, { "description": "flat", "image": "words/flat.png", "voice": "voices-$CA/$LOCALE/words/flat.$CA" }, { "description": "front", "image": "words/front.png", "voice": "voices-$CA/$LOCALE/words/front.$CA" }, { "description": "full", "image": "words/full.png", "voice": "voices-$CA/$LOCALE/words/full.$CA" }, { "description": "giant", "image": "words/giant.png", "voice": "voices-$CA/$LOCALE/words/giant.$CA" }, { "description": "golden", "image": "words/golden.png", "voice": "voices-$CA/$LOCALE/words/golden.$CA" }, { "description": "half", "image": "words/half.png", "voice": "voices-$CA/$LOCALE/words/half.$CA" }, { "description": "happy", "image": "words/happy.png", "voice": "voices-$CA/$LOCALE/words/happy.$CA" }, { "description": "high", "image": "words/high.png", "voice": "voices-$CA/$LOCALE/words/high.$CA" }, { "description": "hot", "image": "words/hot.png", "voice": "voices-$CA/$LOCALE/words/hot.$CA" }, { "description": "huge", "image": "words/huge.png", "voice": "voices-$CA/$LOCALE/words/huge.$CA" }, { "description": "hunchback", "image": "words/hunchback.png", "voice": "voices-$CA/$LOCALE/words/hunchback.$CA" }, { "description": "ill", "image": "words/ill.png", "voice": "voices-$CA/$LOCALE/words/ill.$CA" }, { "description": "left", "image": "words/left.png", "voice": "voices-$CA/$LOCALE/words/left.$CA" }, { "description": "line", "image": "words/line.png", "voice": "voices-$CA/$LOCALE/words/line.$CA" }, { "description": "liquid", "image": "words/liquid.png", "voice": "voices-$CA/$LOCALE/words/liquid.$CA" }, { "description": "magic", "image": "words/magic.png", "voice": "voices-$CA/$LOCALE/words/magic.$CA" }, { "description": "naked", "image": "words/naked.png", "voice": "voices-$CA/$LOCALE/words/naked.$CA" }, { "description": "on", "image": "words/on.png", "voice": "voices-$CA/$LOCALE/words/on.$CA" }, { "description": "open", "image": "words/open.png", "voice": "voices-$CA/$LOCALE/words/open.$CA" }, { "description": "petite", "image": "words/petite.png", "voice": "voices-$CA/$LOCALE/words/petite.$CA" }, { "description": "pretty", "image": "words/pretty.png", "voice": "voices-$CA/$LOCALE/words/pretty.$CA" }, { "description": "right", "image": "words/right.png", "voice": "voices-$CA/$LOCALE/words/right.$CA" }, { "description": "round", "image": "words/round.png", "voice": "voices-$CA/$LOCALE/words/round.$CA" }, { "description": "royal", "image": "words/royal.png", "voice": "voices-$CA/$LOCALE/words/royal.$CA" }, { "description": "shut", "image": "words/shut.png", "voice": "voices-$CA/$LOCALE/words/shut.$CA" }, { "description": "sick", "image": "words/sick.png", "voice": "voices-$CA/$LOCALE/words/sick.$CA" }, { "description": "slim", "image": "words/slim.png", "voice": "voices-$CA/$LOCALE/words/slim.$CA" }, { "description": "sloppy", "image": "words/sloppy.png", "voice": "voices-$CA/$LOCALE/words/sloppy.$CA" }, { "description": "small", "image": "words/small.png", "voice": "voices-$CA/$LOCALE/words/small.$CA" }, { "description": "spot", "image": "words/spot.png", "voice": "voices-$CA/$LOCALE/words/spot.$CA" }, { "description": "square", "image": "words/square.png", "voice": "voices-$CA/$LOCALE/words/square.$CA" }, { "description": "stand", "image": "words/stand.png", "voice": "voices-$CA/$LOCALE/words/stand.$CA" }, { "description": "steep", "image": "words/steep.png", "voice": "voices-$CA/$LOCALE/words/steep.$CA" }, { "description": "stinky", "image": "words/stinky.png", "voice": "voices-$CA/$LOCALE/words/stinky.$CA" }, { "description": "straight", "image": "words/straight.png", "voice": "voices-$CA/$LOCALE/words/straight.$CA" }, { "description": "stripe", "image": "words/stripe.png", "voice": "voices-$CA/$LOCALE/words/stripe.$CA" }, { "description": "strong", "image": "words/strong.png", "voice": "voices-$CA/$LOCALE/words/strong.$CA" }, { "description": "tall", "image": "words/tall.png", "voice": "voices-$CA/$LOCALE/words/tall.$CA" }, { "description": "thick", "image": "words/thick.png", "voice": "voices-$CA/$LOCALE/words/thick.$CA" }, { "description": "tired", "image": "words/tired.png", "voice": "voices-$CA/$LOCALE/words/tired.$CA" }, { "description": "triangle", "image": "words/triangle.png", "voice": "voices-$CA/$LOCALE/words/triangle.$CA" }, { "description": "under", "image": "words/under.png", "voice": "voices-$CA/$LOCALE/words/under.$CA" }, { "description": "wet", "image": "words/wet.png", "voice": "voices-$CA/$LOCALE/words/wet.$CA" }, { "description": "wide", "image": "words/wide.png", "voice": "voices-$CA/$LOCALE/words/wide.$CA" } ] }, { "type": "lesson", "name": "color", "content": [ { "description": "color", "image": "words/color.png", "voice": "voices-$CA/$LOCALE/words/color.$CA" }, { "description": "blue", "image": "words/blue.png", "voice": "voices-$CA/$LOCALE/words/blue.$CA" }, { "description": "brown", "image": "words/brown.png", "voice": "voices-$CA/$LOCALE/words/brown.$CA" }, { "description": "black", "image": "words/black.png", "voice": "voices-$CA/$LOCALE/words/black.$CA" }, { "description": "gray", "image": "words/gray.png", "voice": "voices-$CA/$LOCALE/words/gray.$CA" }, { "description": "green", "image": "words/green.png", "voice": "voices-$CA/$LOCALE/words/green.$CA" }, { "description": "mauve", "image": "words/mauve.png", "voice": "voices-$CA/$LOCALE/words/mauve.$CA" }, { "description": "orange color", "image": "words/orange-color.png", "voice": "voices-$CA/$LOCALE/words/orange-color.$CA" }, { "description": "pink", "image": "words/pink.png", "voice": "voices-$CA/$LOCALE/words/pink.$CA" }, { "description": "red", "image": "words/red.png", "voice": "voices-$CA/$LOCALE/words/red.$CA" }, { "description": "white", "image": "words/white.png", "voice": "voices-$CA/$LOCALE/words/white.$CA" }, { "description": "yellow", "image": "words/yellow.png", "voice": "voices-$CA/$LOCALE/words/yellow.$CA" } ] }, { "type": "lesson", "name": "number", "content": [ { "description": "zero", "image": "words/zero.png", "voice": "voices-$CA/$LOCALE/alphabet/U0030.$CA" }, { "description": "one", "image": "words/one.png", "voice": "voices-$CA/$LOCALE/alphabet/U0031.$CA" }, { "description": "two", "image": "words/two.png", "voice": "voices-$CA/$LOCALE/alphabet/U0032.$CA" }, { "description": "three", "image": "words/three.png", "voice": "voices-$CA/$LOCALE/alphabet/U0033.$CA" }, { "description": "four", "image": "words/four.png", "voice": "voices-$CA/$LOCALE/alphabet/U0034.$CA" }, { "description": "five", "image": "words/five.png", "voice": "voices-$CA/$LOCALE/alphabet/U0035.$CA" }, { "description": "six", "image": "words/six.png", "voice": "voices-$CA/$LOCALE/alphabet/U0036.$CA" }, { "description": "seven", "image": "words/seven.png", "voice": "voices-$CA/$LOCALE/alphabet/U0037.$CA" }, { "description": "eight", "image": "words/eight.png", "voice": "voices-$CA/$LOCALE/alphabet/U0038.$CA" }, { "description": "nine", "image": "words/nine.png", "voice": "voices-$CA/$LOCALE/alphabet/U0039.$CA" }, { "description": "ten", "image": "words/ten.png", "voice": "voices-$CA/$LOCALE/alphabet/10.$CA" }, { "description": "eleven", "image": "words/eleven.png", - "voice": "voices-$CA/$LOCALE/alphabet/11.$CA" + "voice": "voices-$CA/$LOCALE/words/eleven.$CA" }, { "description": "twelve", "image": "words/twelve.png", - "voice": "voices-$CA/$LOCALE/alphabet/12.$CA" + "voice": "voices-$CA/$LOCALE/words/twelve.$CA" }, { "description": "sixteen", "image": "words/sixteen.png", - "voice": "voices-$CA/$LOCALE/alphabet/16.$CA" + "voice": "voices-$CA/$LOCALE/words/sixteen.$CA" } ] } ] }, { "type": "chapter", "name": "people", "content": [ { "type": "lesson", "name": "bodyparts", "content": [ { "description": "ankle", "image": "words/ankle.png", "voice": "voices-$CA/$LOCALE/words/ankle.$CA" }, { "description": "arm", "image": "words/arm.png", "voice": "voices-$CA/$LOCALE/words/arm.$CA" }, { "description": "back", "image": "words/back.png", "voice": "voices-$CA/$LOCALE/words/back.$CA" }, { "description": "bald", "image": "words/bald.png", "voice": "voices-$CA/$LOCALE/words/bald.$CA" }, { "description": "beard", "image": "words/beard.png", "voice": "voices-$CA/$LOCALE/words/beard.$CA" }, { "description": "belly", "image": "words/belly.png", "voice": "voices-$CA/$LOCALE/words/belly.$CA" }, { "description": "bottom", "image": "words/bottom.png", "voice": "voices-$CA/$LOCALE/words/bottom.$CA" }, { "description": "braid", "image": "words/braid.png", "voice": "voices-$CA/$LOCALE/words/braid.$CA" }, { "description": "brain", "image": "words/brain.png", "voice": "voices-$CA/$LOCALE/words/brain.$CA" }, { "description": "breast", "image": "words/breast.png", "voice": "voices-$CA/$LOCALE/words/breast.$CA" }, { "description": "bump", "image": "words/bump.png", "voice": "voices-$CA/$LOCALE/words/bump.$CA" }, { "description": "cheek", "image": "words/cheek.png", "voice": "voices-$CA/$LOCALE/words/cheek.$CA" }, { "description": "chin", "image": "words/chin.png", "voice": "voices-$CA/$LOCALE/words/chin.$CA" }, { "description": "ear", "image": "words/ear.png", "voice": "voices-$CA/$LOCALE/words/ear.$CA" }, { "description": "elbow", "image": "words/elbow.png", "voice": "voices-$CA/$LOCALE/words/elbow.$CA" }, { "description": "eyelash", "image": "words/eyelash.png", "voice": "voices-$CA/$LOCALE/words/eyelash.$CA" }, { "description": "eyes", "image": "words/eyes.png", "voice": "voices-$CA/$LOCALE/words/eyes.$CA" }, { "description": "face", "image": "words/face.png", "voice": "voices-$CA/$LOCALE/words/face.$CA" }, { "description": "feet", "image": "words/feet.png", "voice": "voices-$CA/$LOCALE/words/feet.$CA" }, { "description": "femur", "image": "words/femur.png", "voice": "voices-$CA/$LOCALE/words/femur.$CA" }, { "description": "finger", "image": "words/finger.png", "voice": "voices-$CA/$LOCALE/words/finger.$CA" }, { "description": "fist", "image": "words/fist.png", "voice": "voices-$CA/$LOCALE/words/fist.$CA" }, { "description": "foot", "image": "words/foot.png", "voice": "voices-$CA/$LOCALE/words/foot.$CA" }, { "description": "grin", "image": "words/grin.png", "voice": "voices-$CA/$LOCALE/words/grin.$CA" }, { "description": "hair", "image": "words/hair.png", "voice": "voices-$CA/$LOCALE/words/hair.$CA" }, { "description": "hand", "image": "words/hand.png", "voice": "voices-$CA/$LOCALE/words/hand.$CA" }, { "description": "head", "image": "words/head.png", "voice": "voices-$CA/$LOCALE/words/head.$CA" }, { "description": "hip", "image": "words/hip.png", "voice": "voices-$CA/$LOCALE/words/hip.$CA" }, { "description": "knee", "image": "words/knee.png", "voice": "voices-$CA/$LOCALE/words/knee.$CA" }, { "description": "lap", "image": "words/lap.png", "voice": "voices-$CA/$LOCALE/words/lap.$CA" }, { "description": "leg", "image": "words/leg.png", "voice": "voices-$CA/$LOCALE/words/leg.$CA" }, { "description": "lip", "image": "words/lip.png", "voice": "voices-$CA/$LOCALE/words/lip.$CA" }, { "description": "mouth", "image": "words/mouth.png", "voice": "voices-$CA/$LOCALE/words/mouth.$CA" }, { "description": "muscle", "image": "words/muscle.png", "voice": "voices-$CA/$LOCALE/words/muscle.$CA" }, { "description": "navel", "image": "words/navel.png", "voice": "voices-$CA/$LOCALE/words/navel.$CA" }, { "description": "neck", "image": "words/neck.png", "voice": "voices-$CA/$LOCALE/words/neck.$CA" }, { "description": "nose", "image": "words/nose.png", "voice": "voices-$CA/$LOCALE/words/nose.$CA" }, { "description": "nostril", "image": "words/nostril.png", "voice": "voices-$CA/$LOCALE/words/nostril.$CA" }, { "description": "scar", "image": "words/scar.png", "voice": "voices-$CA/$LOCALE/words/scar.$CA" }, { "description": "skeleton", "image": "words/skeleton.png", "voice": "voices-$CA/$LOCALE/words/skeleton.$CA" }, { "description": "skin", "image": "words/skin.png", "voice": "voices-$CA/$LOCALE/words/skin.$CA" }, { "description": "spine", "image": "words/spine.png", "voice": "voices-$CA/$LOCALE/words/spine.$CA" }, { "description": "stare", "image": "words/stare.png", "voice": "voices-$CA/$LOCALE/words/stare.$CA" }, { "description": "stomach", "image": "words/stomach.png", "voice": "voices-$CA/$LOCALE/words/stomach.$CA" }, { "description": "sweat", "image": "words/sweat.png", "voice": "voices-$CA/$LOCALE/words/sweat.$CA" }, { "description": "teeth", "image": "words/teeth.png", "voice": "voices-$CA/$LOCALE/words/teeth.$CA" }, { "description": "thigh", "image": "words/thigh.png", "voice": "voices-$CA/$LOCALE/words/thigh.$CA" }, { "description": "throat", "image": "words/throat.png", "voice": "voices-$CA/$LOCALE/words/throat.$CA" }, { "description": "thumb", "image": "words/thumb.png", "voice": "voices-$CA/$LOCALE/words/thumb.$CA" }, { "description": "toe", "image": "words/toe.png", "voice": "voices-$CA/$LOCALE/words/toe.$CA" }, { "description": "tongue", "image": "words/tongue.png", "voice": "voices-$CA/$LOCALE/words/tongue.$CA" }, { "description": "vein", "image": "words/vein.png", "voice": "voices-$CA/$LOCALE/words/vein.$CA" }, { "description": "wart", "image": "words/wart.png", "voice": "voices-$CA/$LOCALE/words/wart.$CA" }, { "description": "wrist", "image": "words/wrist.png", "voice": "voices-$CA/$LOCALE/words/wrist.$CA" } ] }, { "type": "lesson", "name": "clothes", "content": [ { "description": "bathing suit", "image": "words/bathing_suit.png", "voice": "voices-$CA/$LOCALE/words/bathing_suit.$CA" }, { "description": "bib", "image": "words/bib.png", "voice": "voices-$CA/$LOCALE/words/bib.$CA" }, { "description": "button", "image": "words/button.png", "voice": "voices-$CA/$LOCALE/words/button.$CA" }, { "description": "cap", "image": "words/cap.png", "voice": "voices-$CA/$LOCALE/words/cap.$CA" }, { "description": "cape", "image": "words/cape.png", "voice": "voices-$CA/$LOCALE/words/cape.$CA" }, { "description": "coat", "image": "words/coat.png", "voice": "voices-$CA/$LOCALE/words/coat.$CA" }, { "description": "dress", "image": "words/dress.png", "voice": "voices-$CA/$LOCALE/words/dress.$CA" }, { "description": "glove", "image": "words/glove.png", "voice": "voices-$CA/$LOCALE/words/glove.$CA" }, { "description": "hat", "image": "words/hat.png", "voice": "voices-$CA/$LOCALE/words/hat.$CA" }, { "description": "heel", "image": "words/heel.png", "voice": "voices-$CA/$LOCALE/words/heel.$CA" }, { "description": "helmet", "image": "words/helmet.png", "voice": "voices-$CA/$LOCALE/words/helmet.$CA" }, { "description": "jacket", "image": "words/jacket.png", "voice": "voices-$CA/$LOCALE/words/jacket.$CA" }, { "description": "kimono", "image": "words/kimono.png", "voice": "voices-$CA/$LOCALE/words/kimono.$CA" }, { "description": "nightgown", "image": "words/nightgown.png", "voice": "voices-$CA/$LOCALE/words/nightgown.$CA" }, { "description": "pajamas", "image": "words/pajamas.png", "voice": "voices-$CA/$LOCALE/words/pajamas.$CA" }, { "description": "panties", "image": "words/panties.png", "voice": "voices-$CA/$LOCALE/words/panties.$CA" }, { "description": "pants", "image": "words/pants.png", "voice": "voices-$CA/$LOCALE/words/pants.$CA" }, { "description": "patch", "image": "words/patch.png", "voice": "voices-$CA/$LOCALE/words/patch.$CA" }, { "description": "pocket", "image": "words/pocket.png", "voice": "voices-$CA/$LOCALE/words/pocket.$CA" }, { "description": "pompon", "image": "words/pompon.png", "voice": "voices-$CA/$LOCALE/words/pompon.$CA" }, { "description": "pullover", "image": "words/pullover.png", "voice": "voices-$CA/$LOCALE/words/pullover.$CA" }, { "description": "raincoat", "image": "words/raincoat.png", "voice": "voices-$CA/$LOCALE/words/raincoat.$CA" }, { "description": "robe", "image": "words/robe.png", "voice": "voices-$CA/$LOCALE/words/robe.$CA" }, { "description": "sandals", "image": "words/sandals.png", "voice": "voices-$CA/$LOCALE/words/sandals.$CA" }, { "description": "sash", "image": "words/sash.png", "voice": "voices-$CA/$LOCALE/words/sash.$CA" }, { "description": "scarf", "image": "words/scarf.png", "voice": "voices-$CA/$LOCALE/words/scarf.$CA" }, { "description": "shirt", "image": "words/shirt.png", "voice": "voices-$CA/$LOCALE/words/shirt.$CA" }, { "description": "shoe", "image": "words/shoe.png", "voice": "voices-$CA/$LOCALE/words/shoe.$CA" }, { "description": "shoelace", "image": "words/shoelace.png", "voice": "voices-$CA/$LOCALE/words/shoelace.$CA" }, { "description": "short", "image": "words/short.png", "voice": "voices-$CA/$LOCALE/words/short.$CA" }, { "description": "skirt", "image": "words/skirt.png", "voice": "voices-$CA/$LOCALE/words/skirt.$CA" }, { "description": "sleeve", "image": "words/sleeve.png", "voice": "voices-$CA/$LOCALE/words/sleeve.$CA" }, { "description": "slippers", "image": "words/slippers.png", "voice": "voices-$CA/$LOCALE/words/slippers.$CA" }, { "description": "smock", "image": "words/smock.png", "voice": "voices-$CA/$LOCALE/words/smock.$CA" }, { "description": "sneaker", "image": "words/sneaker.png", "voice": "voices-$CA/$LOCALE/words/sneaker.$CA" }, { "description": "sock", "image": "words/sock.png", "voice": "voices-$CA/$LOCALE/words/sock.$CA" }, { "description": "sole shoe", "image": "words/sole_shoe.png", "voice": "voices-$CA/$LOCALE/words/sole_shoe.$CA" }, { "description": "suit", "image": "words/suit.png", "voice": "voices-$CA/$LOCALE/words/suit.$CA" }, { "description": "sweatshirt", "image": "words/sweatshirt.png", "voice": "voices-$CA/$LOCALE/words/sweatshirt.$CA" }, { "description": "uniform", "image": "words/uniform.png", "voice": "voices-$CA/$LOCALE/words/uniform.$CA" }, { "description": "vest", "image": "words/vest.png", "voice": "voices-$CA/$LOCALE/words/vest.$CA" }, { "description": "zipper", "image": "words/zipper.png", "voice": "voices-$CA/$LOCALE/words/zipper.$CA" } ] }, { "type": "lesson", "name": "emotion", "content": [ { "description": "ache", "image": "words/ache.png", "voice": "voices-$CA/$LOCALE/words/ache.$CA" }, { "description": "angry", "image": "words/angry.png", "voice": "voices-$CA/$LOCALE/words/angry.$CA" }, { "description": "blush", "image": "words/blush.png", "voice": "voices-$CA/$LOCALE/words/blush.$CA" }, { "description": "danger", "image": "words/danger.png", "voice": "voices-$CA/$LOCALE/words/danger.$CA" }, { "description": "frown", "image": "words/frown.png", "voice": "voices-$CA/$LOCALE/words/frown.$CA" }, { "description": "fun", "image": "words/fun.png", "voice": "voices-$CA/$LOCALE/words/fun.$CA" }, { "description": "laugh", "image": "words/laugh.png", "voice": "voices-$CA/$LOCALE/words/laugh.$CA" }, { "description": "mad", "image": "words/mad.png", "voice": "voices-$CA/$LOCALE/words/mad.$CA" }, { "description": "rage", "image": "words/rage.png", "voice": "voices-$CA/$LOCALE/words/rage.$CA" }, { "description": "sad", "image": "words/sad.png", "voice": "voices-$CA/$LOCALE/words/sad.$CA" }, { "description": "smile", "image": "words/smile.png", "voice": "voices-$CA/$LOCALE/words/smile.$CA" }, { "description": "sob", "image": "words/sob.png", "voice": "voices-$CA/$LOCALE/words/sob.$CA" }, { "description": "yum", "image": "words/yum.png", "voice": "voices-$CA/$LOCALE/words/yum.$CA" } ] }, { "type": "lesson", "name": "job", "content": [ { "description": "accountant", "image": "words/accountant.png", "voice": "voices-$CA/$LOCALE/words/accountant.$CA" }, { "description": "actor", "image": "words/actor.png", "voice": "voices-$CA/$LOCALE/words/actor.$CA" }, { "description": "artist", "image": "words/artist.png", "voice": "voices-$CA/$LOCALE/words/artist.$CA" }, { "description": "astronaut", "image": "words/astronaut.png", "voice": "voices-$CA/$LOCALE/words/astronaut.$CA" }, { "description": "boxer", "image": "words/boxer.png", "voice": "voices-$CA/$LOCALE/words/boxer.$CA" }, { "description": "banker female", "image": "words/banker_female.png", "voice": "voices-$CA/$LOCALE/words/banker_female.$CA" }, { "description": "bricklayer", "image": "words/bricklayer.png", "voice": "voices-$CA/$LOCALE/words/bricklayer.$CA" }, { "description": "butcher", "image": "words/butcher.png", "voice": "voices-$CA/$LOCALE/words/butcher.$CA" }, { "description": "carpenter", "image": "words/carpenter.png", "voice": "voices-$CA/$LOCALE/words/carpenter.$CA" }, { "description": "chef", "image": "words/chef.png", "voice": "voices-$CA/$LOCALE/words/chef.$CA" }, { "description": "chief", "image": "words/chief.png", "voice": "voices-$CA/$LOCALE/words/chief.$CA" }, { "description": "cleaning lady", "image": "words/cleaning_lady.png", "voice": "voices-$CA/$LOCALE/words/cleaning_lady.$CA" }, { "description": "clown", "image": "words/clown.png", "voice": "voices-$CA/$LOCALE/words/clown.$CA" }, { "description": "coach", "image": "words/coach.png", "voice": "voices-$CA/$LOCALE/words/coach.$CA" }, { "description": "cowboy", "image": "words/cowboy.png", "voice": "voices-$CA/$LOCALE/words/cowboy.$CA" }, { "description": "criminal", "image": "words/criminal.png", "voice": "voices-$CA/$LOCALE/words/criminal.$CA" }, { "description": "doctor", "image": "words/doctor.png", "voice": "voices-$CA/$LOCALE/words/doctor.$CA" }, { "description": "electrician", "image": "words/electrician.png", "voice": "voices-$CA/$LOCALE/words/electrician.$CA" }, { "description": "engineer", "image": "words/engineer.png", "voice": "voices-$CA/$LOCALE/words/engineer.$CA" }, { "description": "farmer", "image": "words/farmer.png", "voice": "voices-$CA/$LOCALE/words/farmer.$CA" }, { "description": "fireman", "image": "words/fireman.png", "voice": "voices-$CA/$LOCALE/words/fireman.$CA" }, { "description": "fisherman", "image": "words/fisherman.png", "voice": "voices-$CA/$LOCALE/words/fisherman.$CA" }, { "description": "hunter", "image": "words/hunter.png", "voice": "voices-$CA/$LOCALE/words/hunter.$CA" }, { "description": "job", "image": "words/job.png", "voice": "voices-$CA/$LOCALE/words/job.$CA" }, { "description": "jockey", "image": "words/jockey.png", "voice": "voices-$CA/$LOCALE/words/jockey.$CA" }, { "description": "judge", "image": "words/judge.png", "voice": "voices-$CA/$LOCALE/words/judge.$CA" }, { "description": "juggler", "image": "words/juggler.png", "voice": "voices-$CA/$LOCALE/words/juggler.$CA" }, { "description": "king", "image": "words/king.png", "voice": "voices-$CA/$LOCALE/words/king.$CA" }, { "description": "knight", "image": "words/knight.png", "voice": "voices-$CA/$LOCALE/words/knight.$CA" }, { "description": "lad", "image": "words/lad.png", "voice": "voices-$CA/$LOCALE/words/lad.$CA" }, { "description": "lawyer", "image": "words/lawyer.png", "voice": "voices-$CA/$LOCALE/words/lawyer.$CA" }, { "description": "librarian", "image": "words/librarian.png", "voice": "voices-$CA/$LOCALE/words/librarian.$CA" }, { "description": "mechanic", "image": "words/mechanic.png", "voice": "voices-$CA/$LOCALE/words/mechanic.$CA" }, { "description": "musician", "image": "words/musician.png", "voice": "voices-$CA/$LOCALE/words/musician.$CA" }, { "description": "nun", "image": "words/nun.png", "voice": "voices-$CA/$LOCALE/words/nun.$CA" }, { "description": "nurse", "image": "words/nurse.png", "voice": "voices-$CA/$LOCALE/words/nurse.$CA" }, { "description": "nurse male", "image": "words/nurse_male.png", "voice": "voices-$CA/$LOCALE/words/nurse_male.$CA" }, { "description": "pilot", "image": "words/pilot.png", "voice": "voices-$CA/$LOCALE/words/pilot.$CA" }, { "description": "pirate", "image": "words/pirate.png", "voice": "voices-$CA/$LOCALE/words/pirate.$CA" }, { "description": "plumber", "image": "words/plumber.png", "voice": "voices-$CA/$LOCALE/words/plumber.$CA" }, { "description": "police", "image": "words/police.png", "voice": "voices-$CA/$LOCALE/words/police.$CA" }, { "description": "pope", "image": "words/pope.png", "voice": "voices-$CA/$LOCALE/words/pope.$CA" }, { "description": "president", "image": "words/president.png", "voice": "voices-$CA/$LOCALE/words/president.$CA" }, { "description": "priest", "image": "words/priest.png", "voice": "voices-$CA/$LOCALE/words/priest.$CA" }, { "description": "prince", "image": "words/prince.png", "voice": "voices-$CA/$LOCALE/words/prince.$CA" }, { "description": "princess", "image": "words/princess.png", "voice": "voices-$CA/$LOCALE/words/princess.$CA" }, { "description": "prisoner", "image": "words/prisoner.png", "voice": "voices-$CA/$LOCALE/words/prisoner.$CA" }, { "description": "queen", "image": "words/queen.png", "voice": "voices-$CA/$LOCALE/words/queen.$CA" }, { "description": "sailor", "image": "words/sailor.png", "voice": "voices-$CA/$LOCALE/words/sailor.$CA" }, { "description": "soldier", "image": "words/soldier.png", "voice": "voices-$CA/$LOCALE/words/soldier.$CA" }, { "description": "female soldier", "image": "words/soldier_female.png", "voice": "voices-$CA/$LOCALE/words/soldier_female.$CA" }, { "description": "student", "image": "words/student.png", "voice": "voices-$CA/$LOCALE/words/student.$CA" }, { "description": "teacher", "image": "words/teacher.png", "voice": "voices-$CA/$LOCALE/words/teacher.$CA" }, { "description": "thief", "image": "words/thief.png", "voice": "voices-$CA/$LOCALE/words/thief.$CA" }, { "description": "vet", "image": "words/vet.png", "voice": "voices-$CA/$LOCALE/words/vet.$CA" }, { "description": "worker", "image": "words/worker.png", "voice": "voices-$CA/$LOCALE/words/worker.$CA" } ] }, { "type": "lesson", "name": "people", "content": [ { "description": "angel", "image": "words/angel.png", "voice": "voices-$CA/$LOCALE/words/angel.$CA" }, { "description": "fairy", "image": "words/fairy.png", "voice": "voices-$CA/$LOCALE/words/fairy.$CA" }, { "description": "wedding", "image": "words/wedding.png", "voice": "voices-$CA/$LOCALE/words/wedding.$CA" }, { "description": "boy", "image": "words/boy.png", "voice": "voices-$CA/$LOCALE/words/boy.$CA" }, { "description": "bride", "image": "words/bride.png", "voice": "voices-$CA/$LOCALE/words/bride.$CA" }, { "description": "brother", "image": "words/brother.png", "voice": "voices-$CA/$LOCALE/words/brother.$CA" }, { "description": "child", "image": "words/child.png", "voice": "voices-$CA/$LOCALE/words/child.$CA" }, { "description": "couple", "image": "words/couple.png", "voice": "voices-$CA/$LOCALE/words/couple.$CA" }, { "description": "dad", "image": "words/dad.png", "voice": "voices-$CA/$LOCALE/words/dad.$CA" }, { "description": "dwarf", "image": "words/dwarf.png", "voice": "voices-$CA/$LOCALE/words/dwarf.$CA" }, { "description": "family", "image": "words/family.png", "voice": "voices-$CA/$LOCALE/words/family.$CA" }, { "description": "friend", "image": "words/friend.png", "voice": "voices-$CA/$LOCALE/words/friend.$CA" }, { "description": "girl", "image": "words/girl.png", "voice": "voices-$CA/$LOCALE/words/girl.$CA" }, { "description": "gnome", "image": "words/gnome.png", "voice": "voices-$CA/$LOCALE/words/gnome.$CA" }, { "description": "grandmother", "image": "words/grandmother.png", "voice": "voices-$CA/$LOCALE/words/grandmother.$CA" }, { "description": "husband", "image": "words/husband.png", "voice": "voices-$CA/$LOCALE/words/husband.$CA" }, { "description": "lady", "image": "words/lady.png", "voice": "voices-$CA/$LOCALE/words/lady.$CA" }, { "description": "man", "image": "words/man.png", "voice": "voices-$CA/$LOCALE/words/man.$CA" }, { "description": "mate", "image": "words/mate.png", "voice": "voices-$CA/$LOCALE/words/mate.$CA" }, { "description": "mate female", "image": "words/mate_female.png", "voice": "voices-$CA/$LOCALE/words/mate_female.$CA" }, { "description": "mate male", "image": "words/mate_male.png", "voice": "voices-$CA/$LOCALE/words/mate_male.$CA" }, { "description": "men", "image": "words/men.png", "voice": "voices-$CA/$LOCALE/words/men.$CA" }, { "description": "mom", "image": "words/mom.png", "voice": "voices-$CA/$LOCALE/words/mom.$CA" }, { "description": "mother", "image": "words/mother.png", "voice": "voices-$CA/$LOCALE/words/mother.$CA" }, { "description": "pal", "image": "words/pal.png", "voice": "voices-$CA/$LOCALE/words/pal.$CA" }, { "description": "people", "image": "words/people.png", "voice": "voices-$CA/$LOCALE/words/people.$CA" }, { "description": "sister", "image": "words/sister.png", "voice": "voices-$CA/$LOCALE/words/sister.$CA" }, { "description": "son", "image": "words/son.png", "voice": "voices-$CA/$LOCALE/words/son.$CA" }, { "description": "tribe", "image": "words/tribe.png", "voice": "voices-$CA/$LOCALE/words/tribe.$CA" }, { "description": "twin boys", "image": "words/twin_boys.png", "voice": "voices-$CA/$LOCALE/words/twin_boys.$CA" }, { "description": "twin girls", "image": "words/twin_girls.png", "voice": "voices-$CA/$LOCALE/words/twin_girls.$CA" }, { "description": "wife", "image": "words/wife.png", "voice": "voices-$CA/$LOCALE/words/wife.$CA" }, { "description": "woman", "image": "words/woman.png", "voice": "voices-$CA/$LOCALE/words/woman.$CA" } ] }, { "type": "lesson", "name": "sport", "content": [ { "description": "athlete", "image": "words/athlete.png", "voice": "voices-$CA/$LOCALE/words/athlete.$CA" }, { "description": "ballet", "image": "words/ballet.png", "voice": "voices-$CA/$LOCALE/words/ballet.$CA" }, { "description": "camp", "image": "words/camp.png", "voice": "voices-$CA/$LOCALE/words/camp.$CA" }, { "description": "cheer", "image": "words/cheer.png", "voice": "voices-$CA/$LOCALE/words/cheer.$CA" }, { "description": "climb", "image": "words/climb.png", "voice": "voices-$CA/$LOCALE/words/climb.$CA" }, { "description": "dance", "image": "words/dance.png", "voice": "voices-$CA/$LOCALE/words/dance.$CA" }, { "description": "dive", "image": "words/dive.png", "voice": "voices-$CA/$LOCALE/words/dive.$CA" }, { "description": "explore", "image": "words/explore.png", "voice": "voices-$CA/$LOCALE/words/explore.$CA" }, { "description": "golf", "image": "words/golf.png", "voice": "voices-$CA/$LOCALE/words/golf.$CA" }, { "description": "hike", "image": "words/hike.png", "voice": "voices-$CA/$LOCALE/words/hike.$CA" }, { "description": "hit", "image": "words/hit.png", "voice": "voices-$CA/$LOCALE/words/hit.$CA" }, { "description": "hockey", "image": "words/hockey.png", "voice": "voices-$CA/$LOCALE/words/hockey.$CA" }, { "description": "hop", "image": "words/hop.png", "voice": "voices-$CA/$LOCALE/words/hop.$CA" }, { "description": "jog", "image": "words/jog.png", "voice": "voices-$CA/$LOCALE/words/jog.$CA" }, { "description": "judo", "image": "words/judo.png", "voice": "voices-$CA/$LOCALE/words/judo.$CA" }, { "description": "lift", "image": "words/lift.png", "voice": "voices-$CA/$LOCALE/words/lift.$CA" }, { "description": "race", "image": "words/race.png", "voice": "voices-$CA/$LOCALE/words/race.$CA" }, { "description": "ran", "image": "words/ran.png", "voice": "voices-$CA/$LOCALE/words/ran.$CA" }, { "description": "ride", "image": "words/ride.png", "voice": "voices-$CA/$LOCALE/words/ride.$CA" }, { "description": "rowing", "image": "words/rowing.png", "voice": "voices-$CA/$LOCALE/words/rowing.$CA" }, { "description": "skate", "image": "words/skate.png", "voice": "voices-$CA/$LOCALE/words/skate.$CA" }, { "description": "ski", "image": "words/ski.png", "voice": "voices-$CA/$LOCALE/words/ski.$CA" }, { "description": "sport", "image": "words/sport.png", "voice": "voices-$CA/$LOCALE/words/sport.$CA" }, { "description": "swim", "image": "words/swim.png", "voice": "voices-$CA/$LOCALE/words/swim.$CA" }, { "description": "tennis", "image": "words/tennis.png", "voice": "voices-$CA/$LOCALE/words/tennis.$CA" }, { "description": "throw", "image": "words/throw.png", "voice": "voices-$CA/$LOCALE/words/throw.$CA" } ] }, { "type": "lesson", "name": "food", "content": [ { "description": "beef", "image": "words/beef.png", "voice": "voices-$CA/$LOCALE/words/beef.$CA" }, { "description": "bone", "image": "words/bone.png", "voice": "voices-$CA/$LOCALE/words/bone.$CA" }, { "description": "bread", "image": "words/bread.png", "voice": "voices-$CA/$LOCALE/words/bread.$CA" }, { "description": "bun", "image": "words/bun.png", "voice": "voices-$CA/$LOCALE/words/bun.$CA" }, { "description": "butter", "image": "words/butter.png", "voice": "voices-$CA/$LOCALE/words/butter.$CA" }, { "description": "cake", "image": "words/cake.png", "voice": "voices-$CA/$LOCALE/words/cake.$CA" }, { "description": "candy", "image": "words/candy.png", "voice": "voices-$CA/$LOCALE/words/candy.$CA" }, { "description": "cereal", "image": "words/cereal.png", "voice": "voices-$CA/$LOCALE/words/cereal.$CA" }, { "description": "cheese", "image": "words/cheese.png", "voice": "voices-$CA/$LOCALE/words/cheese.$CA" }, { "description": "chocolate", "image": "words/chocolate.png", "voice": "voices-$CA/$LOCALE/words/chocolate.$CA" }, { "description": "coffee", "image": "words/coffee.png", "voice": "voices-$CA/$LOCALE/words/coffee.$CA" }, { "description": "cookie", "image": "words/cookie.png", "voice": "voices-$CA/$LOCALE/words/cookie.$CA" }, { "description": "crepe", "image": "words/crepe.png", "voice": "voices-$CA/$LOCALE/words/crepe.$CA" }, { "description": "crumb", "image": "words/crumb.png", "voice": "voices-$CA/$LOCALE/words/crumb.$CA" }, { "description": "crust", "image": "words/crust.png", "voice": "voices-$CA/$LOCALE/words/crust.$CA" }, { "description": "dessert", "image": "words/dessert.png", "voice": "voices-$CA/$LOCALE/words/dessert.$CA" }, { "description": "doughnut", "image": "words/doughnut.png", "voice": "voices-$CA/$LOCALE/words/doughnut.$CA" }, { "description": "drink", "image": "words/drink.png", "voice": "voices-$CA/$LOCALE/words/drink.$CA" }, { "description": "egg", "image": "words/egg.png", "voice": "voices-$CA/$LOCALE/words/egg.$CA" }, { "description": "feast", "image": "words/feast.png", "voice": "voices-$CA/$LOCALE/words/feast.$CA" }, { "description": "flour", "image": "words/flour.png", "voice": "voices-$CA/$LOCALE/words/flour.$CA" }, { "description": "food", "image": "words/food.png", "voice": "voices-$CA/$LOCALE/words/food.$CA" }, { "description": "fries", "image": "words/fries.png", "voice": "voices-$CA/$LOCALE/words/fries.$CA" }, { "description": "fudge", "image": "words/fudge.png", "voice": "voices-$CA/$LOCALE/words/fudge.$CA" }, { "description": "gum", "image": "words/gum.png", "voice": "voices-$CA/$LOCALE/words/gum.$CA" }, { "description": "ham", "image": "words/ham.png", "voice": "voices-$CA/$LOCALE/words/ham.$CA" }, { "description": "hot dog", "image": "words/hot_dog.png", "voice": "voices-$CA/$LOCALE/words/hot_dog.$CA" }, { "description": "ice", "image": "words/ice.png", "voice": "voices-$CA/$LOCALE/words/ice.$CA" }, { "description": "jam", "image": "words/jam.png", "voice": "voices-$CA/$LOCALE/words/jam.$CA" }, { "description": "jelly", "image": "words/jelly.png", "voice": "voices-$CA/$LOCALE/words/jelly.$CA" }, { "description": "juice", "image": "words/juice.png", "voice": "voices-$CA/$LOCALE/words/juice.$CA" }, { "description": "lemonade", "image": "words/lemonade.png", "voice": "voices-$CA/$LOCALE/words/lemonade.$CA" }, { "description": "lunch", "image": "words/lunch.png", "voice": "voices-$CA/$LOCALE/words/lunch.$CA" }, { "description": "mashed potatoes", "image": "words/mashed_potatoes.png", "voice": "voices-$CA/$LOCALE/words/mashed_potatoes.$CA" }, { "description": "meal", "image": "words/meal.png", "voice": "voices-$CA/$LOCALE/words/meal.$CA" }, { "description": "meat", "image": "words/meat.png", "voice": "voices-$CA/$LOCALE/words/meat.$CA" }, { "description": "milk", "image": "words/milk.png", "voice": "voices-$CA/$LOCALE/words/milk.$CA" }, { "description": "pepper", "image": "words/pepper.png", "voice": "voices-$CA/$LOCALE/words/pepper.$CA" }, { "description": "pie", "image": "words/pie.png", "voice": "voices-$CA/$LOCALE/words/pie.$CA" }, { "description": "pizza", "image": "words/pizza.png", "voice": "voices-$CA/$LOCALE/words/pizza.$CA" }, { "description": "pop", "image": "words/pop.png", "voice": "voices-$CA/$LOCALE/words/pop.$CA" }, { "description": "rice", "image": "words/rice.png", "voice": "voices-$CA/$LOCALE/words/rice.$CA" }, { "description": "roast", "image": "words/roast.png", "voice": "voices-$CA/$LOCALE/words/roast.$CA" }, { "description": "sandwich", "image": "words/sandwich.png", "voice": "voices-$CA/$LOCALE/words/sandwich.$CA" }, { "description": "sauce", "image": "words/sauce.png", "voice": "voices-$CA/$LOCALE/words/sauce.$CA" }, { "description": "sausage", "image": "words/sausage.png", "voice": "voices-$CA/$LOCALE/words/sausage.$CA" }, { "description": "snack", "image": "words/snack.png", "voice": "voices-$CA/$LOCALE/words/snack.$CA" }, { "description": "soup", "image": "words/soup.png", "voice": "voices-$CA/$LOCALE/words/soup.$CA" }, { "description": "spaghetti", "image": "words/spaghetti.png", "voice": "voices-$CA/$LOCALE/words/spaghetti.$CA" }, { "description": "spice", "image": "words/spice.png", "voice": "voices-$CA/$LOCALE/words/spice.$CA" }, { "description": "sprinkle", "image": "words/sprinkle.png", "voice": "voices-$CA/$LOCALE/words/sprinkle.$CA" }, { "description": "steak", "image": "words/steak.png", "voice": "voices-$CA/$LOCALE/words/steak.$CA" }, { "description": "stew", "image": "words/stew.png", "voice": "voices-$CA/$LOCALE/words/stew.$CA" }, { "description": "sugar", "image": "words/sugar.png", "voice": "voices-$CA/$LOCALE/words/sugar.$CA" }, { "description": "treat", "image": "words/treat.png", "voice": "voices-$CA/$LOCALE/words/treat.$CA" }, { "description": "yogurt", "image": "words/yogurt.png", "voice": "voices-$CA/$LOCALE/words/yogurt.$CA" } ] } ] }, { "type": "chapter", "name": "nature", "content": [ { "type": "lesson", "name": "animal", "content": [ { "description": "alligator", "image": "words/alligator.png", "voice": "voices-$CA/$LOCALE/words/alligator.$CA" }, { "description": "animal", "image": "words/animal.png", "voice": "voices-$CA/$LOCALE/words/animal.$CA" }, { "description": "ant", "image": "words/ant.png", "voice": "voices-$CA/$LOCALE/words/ant.$CA" }, { "description": "anteater", "image": "words/anteater.png", "voice": "voices-$CA/$LOCALE/words/anteater.$CA" }, { "description": "antelope", "image": "words/antelope.png", "voice": "voices-$CA/$LOCALE/words/antelope.$CA" }, { "description": "bait", "image": "words/bait.png", "voice": "voices-$CA/$LOCALE/words/bait.$CA" }, { "description": "bat", "image": "words/bat.png", "voice": "voices-$CA/$LOCALE/words/bat.$CA" }, { "description": "bear", "image": "words/bear.png", "voice": "voices-$CA/$LOCALE/words/bear.$CA" }, { "description": "beaver", "image": "words/beaver.png", "voice": "voices-$CA/$LOCALE/words/beaver.$CA" }, { "description": "bee", "image": "words/bee.png", "voice": "voices-$CA/$LOCALE/words/bee.$CA" }, { "description": "beetle", "image": "words/beetle.png", "voice": "voices-$CA/$LOCALE/words/beetle.$CA" }, { "description": "bird", "image": "words/bird.png", "voice": "voices-$CA/$LOCALE/words/bird.$CA" }, { "description": "blackbird", "image": "words/blackbird.png", "voice": "voices-$CA/$LOCALE/words/blackbird.$CA" }, { "description": "buffalo", "image": "words/buffalo.png", "voice": "voices-$CA/$LOCALE/words/buffalo.$CA" }, { "description": "bug", "image": "words/bug.png", "voice": "voices-$CA/$LOCALE/words/bug.$CA" }, { "description": "bull", "image": "words/bull.png", "voice": "voices-$CA/$LOCALE/words/bull.$CA" }, { "description": "butterfly", "image": "words/butterfly.png", "voice": "voices-$CA/$LOCALE/words/butterfly.$CA" }, { "description": "camel", "image": "words/camel.png", "voice": "voices-$CA/$LOCALE/words/camel.$CA" }, { "description": "canary", "image": "words/canary.png", "voice": "voices-$CA/$LOCALE/words/canary.$CA" }, { "description": "cat", "image": "words/cat.png", "voice": "voices-$CA/$LOCALE/words/cat.$CA" }, { "description": "caterpillar", "image": "words/caterpillar.png", "voice": "voices-$CA/$LOCALE/words/caterpillar.$CA" }, { "description": "female cat", "image": "words/cat_female.png", "voice": "voices-$CA/$LOCALE/words/cat_female.$CA" }, { "description": "centipede", "image": "words/centipede.png", "voice": "voices-$CA/$LOCALE/words/centipede.$CA" }, { "description": "chameleon", "image": "words/chameleon.png", "voice": "voices-$CA/$LOCALE/words/chameleon.$CA" }, { "description": "chick", "image": "words/chick.png", "voice": "voices-$CA/$LOCALE/words/chick.$CA" }, { "description": "chicken", "image": "words/chicken.png", "voice": "voices-$CA/$LOCALE/words/chicken.$CA" }, { "description": "chimp", "image": "words/chimp.png", "voice": "voices-$CA/$LOCALE/words/chimp.$CA" }, { "description": "clam", "image": "words/clam.png", "voice": "voices-$CA/$LOCALE/words/clam.$CA" }, { "description": "claw", "image": "words/claw.png", "voice": "voices-$CA/$LOCALE/words/claw.$CA" }, { "description": "cobra", "image": "words/cobra.png", "voice": "voices-$CA/$LOCALE/words/cobra.$CA" }, { "description": "cod", "image": "words/cod.png", "voice": "voices-$CA/$LOCALE/words/cod.$CA" }, { "description": "colt", "image": "words/colt.png", "voice": "voices-$CA/$LOCALE/words/colt.$CA" }, { "description": "cow", "image": "words/cow.png", "voice": "voices-$CA/$LOCALE/words/cow.$CA" }, { "description": "crab", "image": "words/crab.png", "voice": "voices-$CA/$LOCALE/words/crab.$CA" }, { "description": "crocodile", "image": "words/crocodile.png", "voice": "voices-$CA/$LOCALE/words/crocodile.$CA" }, { "description": "crow", "image": "words/crow.png", "voice": "voices-$CA/$LOCALE/words/crow.$CA" }, { "description": "deer", "image": "words/deer.png", "voice": "voices-$CA/$LOCALE/words/deer.$CA" }, { "description": "den", "image": "words/den.png", "voice": "voices-$CA/$LOCALE/words/den.$CA" }, { "description": "doe", "image": "words/doe.png", "voice": "voices-$CA/$LOCALE/words/doe.$CA" }, { "description": "dog", "image": "words/dog.png", "voice": "voices-$CA/$LOCALE/words/dog.$CA" }, { "description": "dolphin", "image": "words/dolphin.png", "voice": "voices-$CA/$LOCALE/words/dolphin.$CA" }, { "description": "dove", "image": "words/dove.png", "voice": "voices-$CA/$LOCALE/words/dove.$CA" }, { "description": "dragon", "image": "words/dragon.png", "voice": "voices-$CA/$LOCALE/words/dragon.$CA" }, { "description": "dragonfly", "image": "words/dragonfly.png", "voice": "voices-$CA/$LOCALE/words/dragonfly.$CA" }, { "description": "duck", "image": "words/duck.png", "voice": "voices-$CA/$LOCALE/words/duck.$CA" }, { "description": "duck mother", "image": "words/duck_mother.png", "voice": "voices-$CA/$LOCALE/words/duck_mother.$CA" }, { "description": "eagle", "image": "words/eagle.png", "voice": "voices-$CA/$LOCALE/words/eagle.$CA" }, { "description": "elk", "image": "words/elk.png", "voice": "voices-$CA/$LOCALE/words/elk.$CA" }, { "description": "fawn", "image": "words/fawn.png", "voice": "voices-$CA/$LOCALE/words/fawn.$CA" }, { "description": "feather", "image": "words/feather.png", "voice": "voices-$CA/$LOCALE/words/feather.$CA" }, { "description": "fin", "image": "words/fin.png", "voice": "voices-$CA/$LOCALE/words/fin.$CA" }, { "description": "fish", "image": "words/fish.png", "voice": "voices-$CA/$LOCALE/words/fish.$CA" }, { "description": "flamingo", "image": "words/flamingo.png", "voice": "voices-$CA/$LOCALE/words/flamingo.$CA" }, { "description": "flies", "image": "words/flies.png", "voice": "voices-$CA/$LOCALE/words/flies.$CA" }, { "description": "fox", "image": "words/fox.png", "voice": "voices-$CA/$LOCALE/words/fox.$CA" }, { "description": "frog", "image": "words/frog.png", "voice": "voices-$CA/$LOCALE/words/frog.$CA" }, { "description": "fur", "image": "words/fur.png", "voice": "voices-$CA/$LOCALE/words/fur.$CA" }, { "description": "giraffe", "image": "words/giraffe.png", "voice": "voices-$CA/$LOCALE/words/giraffe.$CA" }, { "description": "goat", "image": "words/goat.png", "voice": "voices-$CA/$LOCALE/words/goat.$CA" }, { "description": "goose", "image": "words/goose.png", "voice": "voices-$CA/$LOCALE/words/goose.$CA" }, { "description": "gorilla", "image": "words/gorilla.png", "voice": "voices-$CA/$LOCALE/words/gorilla.$CA" }, { "description": "guinea pig", "image": "words/guinea_pig.png", "voice": "voices-$CA/$LOCALE/words/guinea_pig.$CA" }, { "description": "hedgehog", "image": "words/hedgehog.png", "voice": "voices-$CA/$LOCALE/words/hedgehog.$CA" }, { "description": "hen", "image": "words/hen.png", "voice": "voices-$CA/$LOCALE/words/hen.$CA" }, { "description": "herd", "image": "words/herd.png", "voice": "voices-$CA/$LOCALE/words/herd.$CA" }, { "description": "hippopotamus", "image": "words/hippopotamus.png", "voice": "voices-$CA/$LOCALE/words/hippopotamus.$CA" }, { "description": "hive", "image": "words/hive.png", "voice": "voices-$CA/$LOCALE/words/hive.$CA" }, { "description": "horse", "image": "words/horse.png", "voice": "voices-$CA/$LOCALE/words/horse.$CA" }, { "description": "hound", "image": "words/hound.png", "voice": "voices-$CA/$LOCALE/words/hound.$CA" }, { "description": "hummingbird", "image": "words/hummingbird.png", "voice": "voices-$CA/$LOCALE/words/hummingbird.$CA" }, { "description": "hyena", "image": "words/hyena.png", "voice": "voices-$CA/$LOCALE/words/hyena.$CA" }, { "description": "iguana", "image": "words/iguana.png", "voice": "voices-$CA/$LOCALE/words/iguana.$CA" }, { "description": "jaguar", "image": "words/jaguar.png", "voice": "voices-$CA/$LOCALE/words/jaguar.$CA" }, { "description": "jay", "image": "words/jay.png", "voice": "voices-$CA/$LOCALE/words/jay.$CA" }, { "description": "jellyfish", "image": "words/jellyfish.png", "voice": "voices-$CA/$LOCALE/words/jellyfish.$CA" }, { "description": "kangaroo", "image": "words/kangaroo.png", "voice": "voices-$CA/$LOCALE/words/kangaroo.$CA" }, { "description": "kitten", "image": "words/kitten.png", "voice": "voices-$CA/$LOCALE/words/kitten.$CA" }, { "description": "koala", "image": "words/koala.png", "voice": "voices-$CA/$LOCALE/words/koala.$CA" }, { "description": "ladybug", "image": "words/ladybug.png", "voice": "voices-$CA/$LOCALE/words/ladybug.$CA" }, { "description": "lama", "image": "words/lama.png", "voice": "voices-$CA/$LOCALE/words/lama.$CA" }, { "description": "lamb", "image": "words/lamb.png", "voice": "voices-$CA/$LOCALE/words/lamb.$CA" }, { "description": "leg animal", "image": "words/leg_animal.png", "voice": "voices-$CA/$LOCALE/words/leg_animal.$CA" }, { "description": "lemur", "image": "words/lemur.png", "voice": "voices-$CA/$LOCALE/words/lemur.$CA" }, { "description": "leopard", "image": "words/leopard.png", "voice": "voices-$CA/$LOCALE/words/leopard.$CA" }, { "description": "lion", "image": "words/lion.png", "voice": "voices-$CA/$LOCALE/words/lion.$CA" }, { "description": "lion cub", "image": "words/lion_cub.png", "voice": "voices-$CA/$LOCALE/words/lion_cub.$CA" }, { "description": "lizard", "image": "words/lizard.png", "voice": "voices-$CA/$LOCALE/words/lizard.$CA" }, { "description": "lobster", "image": "words/lobster.png", "voice": "voices-$CA/$LOCALE/words/lobster.$CA" }, { "description": "magpie", "image": "words/magpie.png", "voice": "voices-$CA/$LOCALE/words/magpie.$CA" }, { "description": "mane", "image": "words/mane.png", "voice": "voices-$CA/$LOCALE/words/mane.$CA" }, { "description": "mice", "image": "words/mice.png", "voice": "voices-$CA/$LOCALE/words/mice.$CA" }, { "description": "mole", "image": "words/mole.png", "voice": "voices-$CA/$LOCALE/words/mole.$CA" }, { "description": "moose", "image": "words/moose.png", "voice": "voices-$CA/$LOCALE/words/moose.$CA" }, { "description": "mosquito", "image": "words/mosquito.png", "voice": "voices-$CA/$LOCALE/words/mosquito.$CA" }, { "description": "mouse", "image": "words/mouse.png", "voice": "voices-$CA/$LOCALE/words/mouse.$CA" }, { "description": "mule", "image": "words/mule.png", "voice": "voices-$CA/$LOCALE/words/mule.$CA" }, { "description": "opossum", "image": "words/opossum.png", "voice": "voices-$CA/$LOCALE/words/opossum.$CA" }, { "description": "ostrich", "image": "words/ostrich.png", "voice": "voices-$CA/$LOCALE/words/ostrich.$CA" }, { "description": "otter", "image": "words/otter.png", "voice": "voices-$CA/$LOCALE/words/otter.$CA" }, { "description": "owl", "image": "words/owl.png", "voice": "voices-$CA/$LOCALE/words/owl.$CA" }, { "description": "ox", "image": "words/ox.png", "voice": "voices-$CA/$LOCALE/words/ox.$CA" }, { "description": "oyster", "image": "words/oyster.png", "voice": "voices-$CA/$LOCALE/words/oyster.$CA" }, { "description": "panda", "image": "words/panda.png", "voice": "voices-$CA/$LOCALE/words/panda.$CA" }, { "description": "panther", "image": "words/panther.png", "voice": "voices-$CA/$LOCALE/words/panther.$CA" }, { "description": "parakeet", "image": "words/parakeet.png", "voice": "voices-$CA/$LOCALE/words/parakeet.$CA" }, { "description": "parrot", "image": "words/parrot.png", "voice": "voices-$CA/$LOCALE/words/parrot.$CA" }, { "description": "paw", "image": "words/paw.png", "voice": "voices-$CA/$LOCALE/words/paw.$CA" }, { "description": "peacock", "image": "words/peacock.png", "voice": "voices-$CA/$LOCALE/words/peacock.$CA" }, { "description": "pelican", "image": "words/pelican.png", "voice": "voices-$CA/$LOCALE/words/pelican.$CA" }, { "description": "pet", "image": "words/pet.png", "voice": "voices-$CA/$LOCALE/words/pet.$CA" }, { "description": "pig", "image": "words/pig.png", "voice": "voices-$CA/$LOCALE/words/pig.$CA" }, { "description": "pigeon", "image": "words/pigeon.png", "voice": "voices-$CA/$LOCALE/words/pigeon.$CA" }, { "description": "piranha", "image": "words/piranha.png", "voice": "voices-$CA/$LOCALE/words/piranha.$CA" }, { "description": "pony", "image": "words/pony.png", "voice": "voices-$CA/$LOCALE/words/pony.$CA" }, { "description": "pug", "image": "words/pug.png", "voice": "voices-$CA/$LOCALE/words/pug.$CA" }, { "description": "puppy", "image": "words/puppy.png", "voice": "voices-$CA/$LOCALE/words/puppy.$CA" }, { "description": "rabbit", "image": "words/rabbit.png", "voice": "voices-$CA/$LOCALE/words/rabbit.$CA" }, { "description": "baby rabbit", "image": "words/rabbit_baby.png", "voice": "voices-$CA/$LOCALE/words/rabbit_baby.$CA" }, { "description": "rat", "image": "words/rat.png", "voice": "voices-$CA/$LOCALE/words/rat.$CA" }, { "description": "reptile", "image": "words/reptile.png", "voice": "voices-$CA/$LOCALE/words/reptile.$CA" }, { "description": "rhinoceros", "image": "words/rhinoceros.png", "voice": "voices-$CA/$LOCALE/words/rhinoceros.$CA" }, { "description": "salamander", "image": "words/salamander.png", "voice": "voices-$CA/$LOCALE/words/salamander.$CA" }, { "description": "salmon", "image": "words/salmon.png", "voice": "voices-$CA/$LOCALE/words/salmon.$CA" }, { "description": "scorpion", "image": "words/scorpion.png", "voice": "voices-$CA/$LOCALE/words/scorpion.$CA" }, { "description": "shark", "image": "words/shark.png", "voice": "voices-$CA/$LOCALE/words/shark.$CA" }, { "description": "sheep", "image": "words/sheep.png", "voice": "voices-$CA/$LOCALE/words/sheep.$CA" }, { "description": "shell", "image": "words/shell.png", "voice": "voices-$CA/$LOCALE/words/shell.$CA" }, { "description": "shrimp", "image": "words/shrimp.png", "voice": "voices-$CA/$LOCALE/words/shrimp.$CA" }, { "description": "skunk", "image": "words/skunk.png", "voice": "voices-$CA/$LOCALE/words/skunk.$CA" }, { "description": "slime", "image": "words/slime.png", "voice": "voices-$CA/$LOCALE/words/slime.$CA" }, { "description": "sloth", "image": "words/sloth.png", "voice": "voices-$CA/$LOCALE/words/sloth.$CA" }, { "description": "slug", "image": "words/slug.png", "voice": "voices-$CA/$LOCALE/words/slug.$CA" }, { "description": "snail", "image": "words/snail.png", "voice": "voices-$CA/$LOCALE/words/snail.$CA" }, { "description": "snake", "image": "words/snake.png", "voice": "voices-$CA/$LOCALE/words/snake.$CA" }, { "description": "sole", "image": "words/sole.png", "voice": "voices-$CA/$LOCALE/words/sole.$CA" }, { "description": "sparrow", "image": "words/sparrow.png", "voice": "voices-$CA/$LOCALE/words/sparrow.$CA" }, { "description": "spider", "image": "words/spider.png", "voice": "voices-$CA/$LOCALE/words/spider.$CA" }, { "description": "spike", "image": "words/spike.png", "voice": "voices-$CA/$LOCALE/words/spike.$CA" }, { "description": "squid", "image": "words/squid.png", "voice": "voices-$CA/$LOCALE/words/squid.$CA" }, { "description": "squirrel", "image": "words/squirrel.png", "voice": "voices-$CA/$LOCALE/words/squirrel.$CA" }, { "description": "starfish", "image": "words/starfish.png", "voice": "voices-$CA/$LOCALE/words/starfish.$CA" }, { "description": "swan", "image": "words/swan.png", "voice": "voices-$CA/$LOCALE/words/swan.$CA" }, { "description": "tadpole", "image": "words/tadpole.png", "voice": "voices-$CA/$LOCALE/words/tadpole.$CA" }, { "description": "tail", "image": "words/tail.png", "voice": "voices-$CA/$LOCALE/words/tail.$CA" }, { "description": "tick", "image": "words/tick.png", "voice": "voices-$CA/$LOCALE/words/tick.$CA" }, { "description": "tiger", "image": "words/tiger.png", "voice": "voices-$CA/$LOCALE/words/tiger.$CA" }, { "description": "toad", "image": "words/toad.png", "voice": "voices-$CA/$LOCALE/words/toad.$CA" }, { "description": "turkey", "image": "words/turkey.png", "voice": "voices-$CA/$LOCALE/words/turkey.$CA" }, { "description": "turtle", "image": "words/turtle.png", "voice": "voices-$CA/$LOCALE/words/turtle.$CA" }, { "description": "tusk", "image": "words/tusk.png", "voice": "voices-$CA/$LOCALE/words/tusk.$CA" }, { "description": "viper", "image": "words/viper.png", "voice": "voices-$CA/$LOCALE/words/viper.$CA" }, { "description": "vulture", "image": "words/vulture.png", "voice": "voices-$CA/$LOCALE/words/vulture.$CA" }, { "description": "wasp", "image": "words/wasp.png", "voice": "voices-$CA/$LOCALE/words/wasp.$CA" }, { "description": "whale", "image": "words/whale.png", "voice": "voices-$CA/$LOCALE/words/whale.$CA" }, { "description": "wing", "image": "words/wing.png", "voice": "voices-$CA/$LOCALE/words/wing.$CA" }, { "description": "wolf", "image": "words/wolf.png", "voice": "voices-$CA/$LOCALE/words/wolf.$CA" }, { "description": "zebra", "image": "words/zebra.png", "voice": "voices-$CA/$LOCALE/words/zebra.$CA" } ] }, { "type": "lesson", "name": "fruit", "content": [ { "description": "apple", "image": "words/apple.png", "voice": "voices-$CA/$LOCALE/words/apple.$CA" }, { "description": "apricot", "image": "words/apricot.png", "voice": "voices-$CA/$LOCALE/words/apricot.$CA" }, { "description": "blackberry", "image": "words/blackberry.png", "voice": "voices-$CA/$LOCALE/words/blackberry.$CA" }, { "description": "blueberry", "image": "words/blueberry.png", "voice": "voices-$CA/$LOCALE/words/blueberry.$CA" }, { "description": "cacao", "image": "words/cacao.png", "voice": "voices-$CA/$LOCALE/words/cacao.$CA" }, { "description": "cherry", "image": "words/cherry.png", "voice": "voices-$CA/$LOCALE/words/cherry.$CA" }, { "description": "coconut", "image": "words/coconut.png", "voice": "voices-$CA/$LOCALE/words/coconut.$CA" }, { "description": "date fruit", "image": "words/date_fruit.png", "voice": "voices-$CA/$LOCALE/words/date_fruit.$CA" }, { "description": "fig", "image": "words/fig.png", "voice": "voices-$CA/$LOCALE/words/fig.$CA" }, { "description": "fruit", "image": "words/fruit.png", "voice": "voices-$CA/$LOCALE/words/fruit.$CA" }, { "description": "grape", "image": "words/grape.png", "voice": "voices-$CA/$LOCALE/words/grape.$CA" }, { "description": "grapefruit", "image": "words/grapefruit.png", "voice": "voices-$CA/$LOCALE/words/grapefruit.$CA" }, { "description": "kernel", "image": "words/kernel.png", "voice": "voices-$CA/$LOCALE/words/kernel.$CA" }, { "description": "kiwi", "image": "words/kiwi.png", "voice": "voices-$CA/$LOCALE/words/kiwi.$CA" }, { "description": "lemon", "image": "words/lemon.png", "voice": "voices-$CA/$LOCALE/words/lemon.$CA" }, { "description": "lime", "image": "words/lime.png", "voice": "voices-$CA/$LOCALE/words/lime.$CA" }, { "description": "mango", "image": "words/mango.png", "voice": "voices-$CA/$LOCALE/words/mango.$CA" }, { "description": "melon", "image": "words/melon.png", "voice": "voices-$CA/$LOCALE/words/melon.$CA" }, { "description": "orange", "image": "words/orange.png", "voice": "voices-$CA/$LOCALE/words/orange.$CA" }, { "description": "papaya", "image": "words/papaya.png", "voice": "voices-$CA/$LOCALE/words/papaya.$CA" }, { "description": "peach", "image": "words/peach.png", "voice": "voices-$CA/$LOCALE/words/peach.$CA" }, { "description": "pear", "image": "words/pear.png", "voice": "voices-$CA/$LOCALE/words/pear.$CA" }, { "description": "plum", "image": "words/plum.png", "voice": "voices-$CA/$LOCALE/words/plum.$CA" }, { "description": "raspberry", "image": "words/raspberry.png", "voice": "voices-$CA/$LOCALE/words/raspberry.$CA" }, { "description": "strawberry", "image": "words/strawberry.png", "voice": "voices-$CA/$LOCALE/words/strawberry.$CA" } ] }, { "type": "lesson", "name": "nature", "content": [ { "description": "bay", "image": "words/bay.png", "voice": "voices-$CA/$LOCALE/words/bay.$CA" }, { "description": "beach", "image": "words/beach.png", "voice": "voices-$CA/$LOCALE/words/beach.$CA" }, { "description": "canyon", "image": "words/canyon.png", "voice": "voices-$CA/$LOCALE/words/canyon.$CA" }, { "description": "cave", "image": "words/cave.png", "voice": "voices-$CA/$LOCALE/words/cave.$CA" }, { "description": "cavern", "image": "words/cavern.png", "voice": "voices-$CA/$LOCALE/words/cavern.$CA" }, { "description": "cliff", "image": "words/cliff.png", "voice": "voices-$CA/$LOCALE/words/cliff.$CA" }, { "description": "cloud", "image": "words/cloud.png", "voice": "voices-$CA/$LOCALE/words/cloud.$CA" }, { "description": "cloudy", "image": "words/cloudy.png", "voice": "voices-$CA/$LOCALE/words/cloudy.$CA" }, { "description": "coast", "image": "words/coast.png", "voice": "voices-$CA/$LOCALE/words/coast.$CA" }, { "description": "cold", "image": "words/cold.png", "voice": "voices-$CA/$LOCALE/words/cold.$CA" }, { "description": "creek", "image": "words/creek.png", "voice": "voices-$CA/$LOCALE/words/creek.$CA" }, { "description": "crystal", "image": "words/crystal.png", "voice": "voices-$CA/$LOCALE/words/crystal.$CA" }, { "description": "desert", "image": "words/desert.png", "voice": "voices-$CA/$LOCALE/words/desert.$CA" }, { "description": "diamond", "image": "words/diamond.png", "voice": "voices-$CA/$LOCALE/words/diamond.$CA" }, { "description": "dirt", "image": "words/dirt.png", "voice": "voices-$CA/$LOCALE/words/dirt.$CA" }, { "description": "drip", "image": "words/drip.png", "voice": "voices-$CA/$LOCALE/words/drip.$CA" }, { "description": "dune", "image": "words/dune.png", "voice": "voices-$CA/$LOCALE/words/dune.$CA" }, { "description": "earth", "image": "words/earth.png", "voice": "voices-$CA/$LOCALE/words/earth.$CA" }, { "description": "fall season", "image": "words/fall_season.png", "voice": "voices-$CA/$LOCALE/words/fall_season.$CA" }, { "description": "fire", "image": "words/fire.png", "voice": "voices-$CA/$LOCALE/words/fire.$CA" }, { "description": "flame", "image": "words/flame.png", "voice": "voices-$CA/$LOCALE/words/flame.$CA" }, { "description": "forest", "image": "words/forest.png", "voice": "voices-$CA/$LOCALE/words/forest.$CA" }, { "description": "garden", "image": "words/garden.png", "voice": "voices-$CA/$LOCALE/words/garden.$CA" }, { "description": "gem", "image": "words/gem.png", "voice": "voices-$CA/$LOCALE/words/gem.$CA" }, { "description": "ground", "image": "words/ground.png", "voice": "voices-$CA/$LOCALE/words/ground.$CA" }, { "description": "heat", "image": "words/heat.png", "voice": "voices-$CA/$LOCALE/words/heat.$CA" }, { "description": "hill", "image": "words/hill.png", "voice": "voices-$CA/$LOCALE/words/hill.$CA" }, { "description": "hole", "image": "words/hole.png", "voice": "voices-$CA/$LOCALE/words/hole.$CA" }, { "description": "iceberg", "image": "words/iceberg.png", "voice": "voices-$CA/$LOCALE/words/iceberg.$CA" }, { "description": "island", "image": "words/island.png", "voice": "voices-$CA/$LOCALE/words/island.$CA" }, { "description": "lake", "image": "words/lake.png", "voice": "voices-$CA/$LOCALE/words/lake.$CA" }, { "description": "land", "image": "words/land.png", "voice": "voices-$CA/$LOCALE/words/land.$CA" }, { "description": "lava", "image": "words/lava.png", "voice": "voices-$CA/$LOCALE/words/lava.$CA" }, { "description": "ledge", "image": "words/ledge.png", "voice": "voices-$CA/$LOCALE/words/ledge.$CA" }, { "description": "lightning", "image": "words/lightning.png", "voice": "voices-$CA/$LOCALE/words/lightning.$CA" }, { "description": "moon", "image": "words/moon.png", "voice": "voices-$CA/$LOCALE/words/moon.$CA" }, { "description": "mountain", "image": "words/mountain.png", "voice": "voices-$CA/$LOCALE/words/mountain.$CA" }, { "description": "mud", "image": "words/mud.png", "voice": "voices-$CA/$LOCALE/words/mud.$CA" }, { "description": "night", "image": "words/night.png", "voice": "voices-$CA/$LOCALE/words/night.$CA" }, { "description": "ocean", "image": "words/ocean.png", "voice": "voices-$CA/$LOCALE/words/ocean.$CA" }, { "description": "path", "image": "words/path.png", "voice": "voices-$CA/$LOCALE/words/path.$CA" }, { "description": "peak", "image": "words/peak.png", "voice": "voices-$CA/$LOCALE/words/peak.$CA" }, { "description": "planet", "image": "words/planet.png", "voice": "voices-$CA/$LOCALE/words/planet.$CA" }, { "description": "pond", "image": "words/pond.png", "voice": "voices-$CA/$LOCALE/words/pond.$CA" }, { "description": "rain", "image": "words/rain.png", "voice": "voices-$CA/$LOCALE/words/rain.$CA" }, { "description": "river", "image": "words/river.png", "voice": "voices-$CA/$LOCALE/words/river.$CA" }, { "description": "rock", "image": "words/rock.png", "voice": "voices-$CA/$LOCALE/words/rock.$CA" }, { "description": "sand", "image": "words/sand.png", "voice": "voices-$CA/$LOCALE/words/sand.$CA" }, { "description": "sea", "image": "words/sea.png", "voice": "voices-$CA/$LOCALE/words/sea.$CA" }, { "description": "shadow", "image": "words/shadow.png", "voice": "voices-$CA/$LOCALE/words/shadow.$CA" }, { "description": "shore", "image": "words/shore.png", "voice": "voices-$CA/$LOCALE/words/shore.$CA" }, { "description": "sky", "image": "words/sky.png", "voice": "voices-$CA/$LOCALE/words/sky.$CA" }, { "description": "slope", "image": "words/slope.png", "voice": "voices-$CA/$LOCALE/words/slope.$CA" }, { "description": "smoke", "image": "words/smoke.png", "voice": "voices-$CA/$LOCALE/words/smoke.$CA" }, { "description": "snow", "image": "words/snow.png", "voice": "voices-$CA/$LOCALE/words/snow.$CA" }, { "description": "spider web", "image": "words/spider_web.png", "voice": "voices-$CA/$LOCALE/words/spider_web.$CA" }, { "description": "spring season", "image": "words/spring_season.png", "voice": "voices-$CA/$LOCALE/words/spring_season.$CA" }, { "description": "star", "image": "words/star.png", "voice": "voices-$CA/$LOCALE/words/star.$CA" }, { "description": "steam", "image": "words/steam.png", "voice": "voices-$CA/$LOCALE/words/steam.$CA" }, { "description": "stone", "image": "words/stone.png", "voice": "voices-$CA/$LOCALE/words/stone.$CA" }, { "description": "stream", "image": "words/stream.png", "voice": "voices-$CA/$LOCALE/words/stream.$CA" }, { "description": "summer", "image": "words/summer.png", "voice": "voices-$CA/$LOCALE/words/summer.$CA" }, { "description": "summit", "image": "words/summit.png", "voice": "voices-$CA/$LOCALE/words/summit.$CA" }, { "description": "sun", "image": "words/sun.png", "voice": "voices-$CA/$LOCALE/words/sun.$CA" }, { "description": "time", "image": "words/time.png", "voice": "voices-$CA/$LOCALE/words/time.$CA" }, { "description": "top", "image": "words/top.png", "voice": "voices-$CA/$LOCALE/words/top.$CA" }, { "description": "trail", "image": "words/trail.png", "voice": "voices-$CA/$LOCALE/words/trail.$CA" }, { "description": "water", "image": "words/water.png", "voice": "voices-$CA/$LOCALE/words/water.$CA" }, { "description": "wave", "image": "words/wave.png", "voice": "voices-$CA/$LOCALE/words/wave.$CA" }, { "description": "wind", "image": "words/wind.png", "voice": "voices-$CA/$LOCALE/words/wind.$CA" }, { "description": "winter", "image": "words/winter.png", "voice": "voices-$CA/$LOCALE/words/winter.$CA" }, { "description": "world", "image": "words/world.png", "voice": "voices-$CA/$LOCALE/words/world.$CA" }, { "description": "vapor", "image": "words/vapor.png", "voice": "voices-$CA/$LOCALE/words/vapor.$CA" } ] }, { "type": "lesson", "name": "plant", "content": [ { "description": "acorn", "image": "words/acorn.png", "voice": "voices-$CA/$LOCALE/words/acorn.$CA" }, { "description": "apple tree", "image": "words/apple_tree.png", "voice": "voices-$CA/$LOCALE/words/apple_tree.$CA" }, { "description": "branch", "image": "words/branch.png", "voice": "voices-$CA/$LOCALE/words/branch.$CA" }, { "description": "bud", "image": "words/bud.png", "voice": "voices-$CA/$LOCALE/words/bud.$CA" }, { "description": "bush", "image": "words/bush.png", "voice": "voices-$CA/$LOCALE/words/bush.$CA" }, { "description": "cactus", "image": "words/cactus.png", "voice": "voices-$CA/$LOCALE/words/cactus.$CA" }, { "description": "clover", "image": "words/clover.png", "voice": "voices-$CA/$LOCALE/words/clover.$CA" }, { "description": "daffodil", "image": "words/daffodil.png", "voice": "voices-$CA/$LOCALE/words/daffodil.$CA" }, { "description": "daisy", "image": "words/daisy.png", "voice": "voices-$CA/$LOCALE/words/daisy.$CA" }, { "description": "dandelion", "image": "words/dandelion.png", "voice": "voices-$CA/$LOCALE/words/dandelion.$CA" }, { "description": "flower", "image": "words/flower.png", "voice": "voices-$CA/$LOCALE/words/flower.$CA" }, { "description": "grain", "image": "words/grain.png", "voice": "voices-$CA/$LOCALE/words/grain.$CA" }, { "description": "grass", "image": "words/grass.png", "voice": "voices-$CA/$LOCALE/words/grass.$CA" }, { "description": "hay", "image": "words/hay.png", "voice": "voices-$CA/$LOCALE/words/hay.$CA" }, { "description": "hedge", "image": "words/hedge.png", "voice": "voices-$CA/$LOCALE/words/hedge.$CA" }, { "description": "lawn", "image": "words/lawn.png", "voice": "voices-$CA/$LOCALE/words/lawn.$CA" }, { "description": "leaf", "image": "words/leaf.png", "voice": "voices-$CA/$LOCALE/words/leaf.$CA" }, { "description": "lilac", "image": "words/lilac.png", "voice": "voices-$CA/$LOCALE/words/lilac.$CA" }, { "description": "maple", "image": "words/maple.png", "voice": "voices-$CA/$LOCALE/words/maple.$CA" }, { "description": "mimosa", "image": "words/mimosa.png", "voice": "voices-$CA/$LOCALE/words/mimosa.$CA" }, { "description": "orchid", "image": "words/orchid.png", "voice": "voices-$CA/$LOCALE/words/orchid.$CA" }, { "description": "palm tree", "image": "words/palm_tree.png", "voice": "voices-$CA/$LOCALE/words/palm_tree.$CA" }, { "description": "peony", "image": "words/peony.png", "voice": "voices-$CA/$LOCALE/words/peony.$CA" }, { "description": "petal", "image": "words/petal.png", "voice": "voices-$CA/$LOCALE/words/petal.$CA" }, { "description": "pine", "image": "words/pine.png", "voice": "voices-$CA/$LOCALE/words/pine.$CA" }, { "description": "pine cone", "image": "words/pine_cone.png", "voice": "voices-$CA/$LOCALE/words/pine_cone.$CA" }, { "description": "pip", "image": "words/pip.png", "voice": "voices-$CA/$LOCALE/words/pip.$CA" }, { "description": "plant", "image": "words/plant.png", "voice": "voices-$CA/$LOCALE/words/plant.$CA" }, { "description": "root", "image": "words/root.png", "voice": "voices-$CA/$LOCALE/words/root.$CA" }, { "description": "rose", "image": "words/rose.png", "voice": "voices-$CA/$LOCALE/words/rose.$CA" }, { "description": "seed", "image": "words/seed.png", "voice": "voices-$CA/$LOCALE/words/seed.$CA" }, { "description": "shrub", "image": "words/shrub.png", "voice": "voices-$CA/$LOCALE/words/shrub.$CA" }, { "description": "stem", "image": "words/stem.png", "voice": "voices-$CA/$LOCALE/words/stem.$CA" }, { "description": "stick", "image": "words/stick.png", "voice": "voices-$CA/$LOCALE/words/stick.$CA" }, { "description": "stump", "image": "words/stump.png", "voice": "voices-$CA/$LOCALE/words/stump.$CA" }, { "description": "tree", "image": "words/tree.png", "voice": "voices-$CA/$LOCALE/words/tree.$CA" }, { "description": "tulip", "image": "words/tulip.png", "voice": "voices-$CA/$LOCALE/words/tulip.$CA" }, { "description": "verdure", "image": "words/verdure.png", "voice": "voices-$CA/$LOCALE/words/verdure.$CA" } ] }, { "type": "lesson", "name": "vegetables", "content": [ { "description": "artichoke", "image": "words/artichoke.png", "voice": "voices-$CA/$LOCALE/words/artichoke.$CA" }, { "description": "asparagus", "image": "words/asparagus.png", "voice": "voices-$CA/$LOCALE/words/asparagus.$CA" }, { "description": "avocado", "image": "words/avocado.png", "voice": "voices-$CA/$LOCALE/words/avocado.$CA" }, { "description": "bean", "image": "words/bean.png", "voice": "voices-$CA/$LOCALE/words/bean.$CA" }, { "description": "broccoli", "image": "words/broccoli.png", "voice": "voices-$CA/$LOCALE/words/broccoli.$CA" }, { "description": "cabbage", "image": "words/cabbage.png", "voice": "voices-$CA/$LOCALE/words/cabbage.$CA" }, { "description": "carrot", "image": "words/carrot.png", "voice": "voices-$CA/$LOCALE/words/carrot.$CA" }, { "description": "cauliflower", "image": "words/cauliflower.png", "voice": "voices-$CA/$LOCALE/words/cauliflower.$CA" }, { "description": "celery", "image": "words/celery.png", "voice": "voices-$CA/$LOCALE/words/celery.$CA" }, { "description": "corn", "image": "words/corn.png", "voice": "voices-$CA/$LOCALE/words/corn.$CA" }, { "description": "cucumber", "image": "words/cucumber.png", "voice": "voices-$CA/$LOCALE/words/cucumber.$CA" }, { "description": "eggplant", "image": "words/eggplant.png", "voice": "voices-$CA/$LOCALE/words/eggplant.$CA" }, { "description": "garlic", "image": "words/garlic.png", "voice": "voices-$CA/$LOCALE/words/garlic.$CA" }, { "description": "leek", "image": "words/leek.png", "voice": "voices-$CA/$LOCALE/words/leek.$CA" }, { "description": "lettuce", "image": "words/lettuce.png", "voice": "voices-$CA/$LOCALE/words/lettuce.$CA" }, { "description": "mushroom", "image": "words/mushroom.png", "voice": "voices-$CA/$LOCALE/words/mushroom.$CA" }, { "description": "nut", "image": "words/nut.png", "voice": "voices-$CA/$LOCALE/words/nut.$CA" }, { "description": "olive", "image": "words/olive.png", "voice": "voices-$CA/$LOCALE/words/olive.$CA" }, { "description": "onion", "image": "words/onion.png", "voice": "voices-$CA/$LOCALE/words/onion.$CA" }, { "description": "pea", "image": "words/pea.png", "voice": "voices-$CA/$LOCALE/words/pea.$CA" }, { "description": "peppers", "image": "words/peppers.png", "voice": "voices-$CA/$LOCALE/words/peppers.$CA" }, { "description": "pod", "image": "words/pod.png", "voice": "voices-$CA/$LOCALE/words/pod.$CA" }, { "description": "potato", "image": "words/potato.png", "voice": "voices-$CA/$LOCALE/words/potato.$CA" }, { "description": "pumpkin", "image": "words/pumpkin.png", "voice": "voices-$CA/$LOCALE/words/pumpkin.$CA" }, { "description": "radish", "image": "words/radish.png", "voice": "voices-$CA/$LOCALE/words/radish.$CA" }, { "description": "spinach", "image": "words/spinach.png", "voice": "voices-$CA/$LOCALE/words/spinach.$CA" }, { "description": "squash", "image": "words/squash.png", "voice": "voices-$CA/$LOCALE/words/squash.$CA" }, { "description": "tomatoe", "image": "words/tomatoe.png", "voice": "voices-$CA/$LOCALE/words/tomatoe.$CA" }, { "description": "turnip", "image": "words/turnip.png", "voice": "voices-$CA/$LOCALE/words/turnip.$CA" }, { "description": "vegetable", "image": "words/vegetable.png", "voice": "voices-$CA/$LOCALE/words/vegetable.$CA" }, { "description": "walnut", "image": "words/walnut.png", "voice": "voices-$CA/$LOCALE/words/walnut.$CA" }, { "description": "wheat", "image": "words/wheat.png", "voice": "voices-$CA/$LOCALE/words/wheat.$CA" } ] } ] }, { "type": "chapter", "name": "object", "content": [ { "type": "lesson", "name": "construction", "content": [ { "description": "bank", "image": "words/bank.png", "voice": "voices-$CA/$LOCALE/words/bank.$CA" }, { "description": "barn", "image": "words/barn.png", "voice": "voices-$CA/$LOCALE/words/barn.$CA" }, { "description": "bedroom", "image": "words/bedroom.png", "voice": "voices-$CA/$LOCALE/words/bedroom.$CA" }, { "description": "big top", "image": "words/big_top.png", "voice": "voices-$CA/$LOCALE/words/big_top.$CA" }, { "description": "bridge", "image": "words/bridge.png", "voice": "voices-$CA/$LOCALE/words/bridge.$CA" }, { "description": "cabin", "image": "words/cabin.png", "voice": "voices-$CA/$LOCALE/words/cabin.$CA" }, { "description": "cage", "image": "words/cage.png", "voice": "voices-$CA/$LOCALE/words/cage.$CA" }, { "description": "castle", "image": "words/castle.png", "voice": "voices-$CA/$LOCALE/words/castle.$CA" }, { "description": "chimney", "image": "words/chimney.png", "voice": "voices-$CA/$LOCALE/words/chimney.$CA" }, { "description": "circus", "image": "words/circus.png", "voice": "voices-$CA/$LOCALE/words/circus.$CA" }, { "description": "city", "image": "words/city.png", "voice": "voices-$CA/$LOCALE/words/city.$CA" }, { "description": "dam", "image": "words/dam.png", "voice": "voices-$CA/$LOCALE/words/dam.$CA" }, { "description": "door", "image": "words/door.png", "voice": "voices-$CA/$LOCALE/words/door.$CA" }, { "description": "fair", "image": "words/fair.png", "voice": "voices-$CA/$LOCALE/words/fair.$CA" }, { "description": "farm", "image": "words/farm.png", "voice": "voices-$CA/$LOCALE/words/farm.$CA" }, { "description": "fountain", "image": "words/fountain.png", "voice": "voices-$CA/$LOCALE/words/fountain.$CA" }, { "description": "garage", "image": "words/garage.png", "voice": "voices-$CA/$LOCALE/words/garage.$CA" }, { "description": "grave", "image": "words/grave.png", "voice": "voices-$CA/$LOCALE/words/grave.$CA" }, { "description": "home", "image": "words/home.png", "voice": "voices-$CA/$LOCALE/words/home.$CA" }, { "description": "hospital", "image": "words/hospital.png", "voice": "voices-$CA/$LOCALE/words/hospital.$CA" }, { "description": "house", "image": "words/house.png", "voice": "voices-$CA/$LOCALE/words/house.$CA" }, { "description": "hut", "image": "words/hut.png", "voice": "voices-$CA/$LOCALE/words/hut.$CA" }, { "description": "kitchen", "image": "words/kitchen.png", "voice": "voices-$CA/$LOCALE/words/kitchen.$CA" }, { "description": "lane", "image": "words/lane.png", "voice": "voices-$CA/$LOCALE/words/lane.$CA" }, { "description": "lighthouse", "image": "words/lighthouse.png", "voice": "voices-$CA/$LOCALE/words/lighthouse.$CA" }, { "description": "merry-go-round", "image": "words/merry-go-round.png", "voice": "voices-$CA/$LOCALE/words/merry-go-round.$CA" }, { "description": "mill", "image": "words/mill.png", "voice": "voices-$CA/$LOCALE/words/mill.$CA" }, { "description": "mosque", "image": "words/mosque.png", "voice": "voices-$CA/$LOCALE/words/mosque.$CA" }, { "description": "office", "image": "words/office.png", "voice": "voices-$CA/$LOCALE/words/office.$CA" }, { "description": "pool", "image": "words/pool.png", "voice": "voices-$CA/$LOCALE/words/pool.$CA" }, { "description": "post", "image": "words/post.png", "voice": "voices-$CA/$LOCALE/words/post.$CA" }, { "description": "prison", "image": "words/prison.png", "voice": "voices-$CA/$LOCALE/words/prison.$CA" }, { "description": "pyramid", "image": "words/pyramid.png", "voice": "voices-$CA/$LOCALE/words/pyramid.$CA" }, { "description": "ramp", "image": "words/ramp.png", "voice": "voices-$CA/$LOCALE/words/ramp.$CA" }, { "description": "road", "image": "words/road.png", "voice": "voices-$CA/$LOCALE/words/road.$CA" }, { "description": "roof", "image": "words/roof.png", "voice": "voices-$CA/$LOCALE/words/roof.$CA" }, { "description": "room", "image": "words/room.png", "voice": "voices-$CA/$LOCALE/words/room.$CA" }, { "description": "school", "image": "words/school.png", "voice": "voices-$CA/$LOCALE/words/school.$CA" }, { "description": "shed", "image": "words/shed.png", "voice": "voices-$CA/$LOCALE/words/shed.$CA" }, { "description": "shop", "image": "words/shop.png", "voice": "voices-$CA/$LOCALE/words/shop.$CA" }, { "description": "shutter", "image": "words/shutter.png", "voice": "voices-$CA/$LOCALE/words/shutter.$CA" }, { "description": "sidewalk", "image": "words/sidewalk.png", "voice": "voices-$CA/$LOCALE/words/sidewalk.$CA" }, { "description": "stage", "image": "words/stage.png", "voice": "voices-$CA/$LOCALE/words/stage.$CA" }, { "description": "staircase", "image": "words/staircase.png", "voice": "voices-$CA/$LOCALE/words/staircase.$CA" }, { "description": "steeple", "image": "words/steeple.png", "voice": "voices-$CA/$LOCALE/words/steeple.$CA" }, { "description": "step", "image": "words/step.png", "voice": "voices-$CA/$LOCALE/words/step.$CA" }, { "description": "store", "image": "words/store.png", "voice": "voices-$CA/$LOCALE/words/store.$CA" }, { "description": "street", "image": "words/street.png", "voice": "voices-$CA/$LOCALE/words/street.$CA" }, { "description": "temple", "image": "words/temple.png", "voice": "voices-$CA/$LOCALE/words/temple.$CA" }, { "description": "tent", "image": "words/tent.png", "voice": "voices-$CA/$LOCALE/words/tent.$CA" }, { "description": "train station", "image": "words/train_station.png", "voice": "voices-$CA/$LOCALE/words/train_station.$CA" }, { "description": "wall", "image": "words/wall.png", "voice": "voices-$CA/$LOCALE/words/wall.$CA" }, { "description": "window", "image": "words/window.png", "voice": "voices-$CA/$LOCALE/words/window.$CA" }, { "description": "window glass", "image": "words/window_glass.png", "voice": "voices-$CA/$LOCALE/words/window_glass.$CA" }, { "description": "zoo", "image": "words/zoo.png", "voice": "voices-$CA/$LOCALE/words/zoo.$CA" }, { "description": "class", "image": "words/class.png", "voice": "voices-$CA/$LOCALE/words/class.$CA" } ] }, { "type": "lesson", "name": "furniture", "content": [ { "description": "armchair", "image": "words/armchair.png", "voice": "voices-$CA/$LOCALE/words/armchair.$CA" }, { "description": "bath", "image": "words/bath.png", "voice": "voices-$CA/$LOCALE/words/bath.$CA" }, { "description": "bed", "image": "words/bed.png", "voice": "voices-$CA/$LOCALE/words/bed.$CA" }, { "description": "bench", "image": "words/bench.png", "voice": "voices-$CA/$LOCALE/words/bench.$CA" }, { "description": "bookcase", "image": "words/bookcase.png", "voice": "voices-$CA/$LOCALE/words/bookcase.$CA" }, { "description": "carpet", "image": "words/carpet.png", "voice": "voices-$CA/$LOCALE/words/carpet.$CA" }, { "description": "chair", "image": "words/chair.png", "voice": "voices-$CA/$LOCALE/words/chair.$CA" }, { "description": "chest", "image": "words/chest.png", "voice": "voices-$CA/$LOCALE/words/chest.$CA" }, { "description": "couch", "image": "words/couch.png", "voice": "voices-$CA/$LOCALE/words/couch.$CA" }, { "description": "cradle", "image": "words/cradle.png", "voice": "voices-$CA/$LOCALE/words/cradle.$CA" }, { "description": "crib", "image": "words/crib.png", "voice": "voices-$CA/$LOCALE/words/crib.$CA" }, { "description": "desk", "image": "words/desk.png", "voice": "voices-$CA/$LOCALE/words/desk.$CA" }, { "description": "doormat", "image": "words/doormat.png", "voice": "voices-$CA/$LOCALE/words/doormat.$CA" }, { "description": "drawer", "image": "words/drawer.png", "voice": "voices-$CA/$LOCALE/words/drawer.$CA" }, { "description": "fan", "image": "words/fan.png", "voice": "voices-$CA/$LOCALE/words/fan.$CA" }, { "description": "lamp", "image": "words/lamp.png", "voice": "voices-$CA/$LOCALE/words/lamp.$CA" }, { "description": "light", "image": "words/light.png", "voice": "voices-$CA/$LOCALE/words/light.$CA" }, { "description": "mat", "image": "words/mat.png", "voice": "voices-$CA/$LOCALE/words/mat.$CA" }, { "description": "mattress", "image": "words/mattress.png", "voice": "voices-$CA/$LOCALE/words/mattress.$CA" }, { "description": "quilt", "image": "words/quilt.png", "voice": "voices-$CA/$LOCALE/words/quilt.$CA" }, { "description": "rug", "image": "words/rug.png", "voice": "voices-$CA/$LOCALE/words/rug.$CA" }, { "description": "seat", "image": "words/seat.png", "voice": "voices-$CA/$LOCALE/words/seat.$CA" }, { "description": "shelf", "image": "words/shelf.png", "voice": "voices-$CA/$LOCALE/words/shelf.$CA" }, { "description": "shower", "image": "words/shower.png", "voice": "voices-$CA/$LOCALE/words/shower.$CA" }, { "description": "sink", "image": "words/sink.png", "voice": "voices-$CA/$LOCALE/words/sink.$CA" }, { "description": "stove", "image": "words/stove.png", "voice": "voices-$CA/$LOCALE/words/stove.$CA" }, { "description": "table", "image": "words/table.png", "voice": "voices-$CA/$LOCALE/words/table.$CA" }, { "description": "television", "image": "words/television.png", "voice": "voices-$CA/$LOCALE/words/television.$CA" }, { "description": "toilet", "image": "words/toilet.png", "voice": "voices-$CA/$LOCALE/words/toilet.$CA" } ] }, { "type": "lesson", "name": "houseware", "content": [ { "description": "appliance", "image": "words/appliance.png", "voice": "voices-$CA/$LOCALE/words/appliance.$CA" }, { "description": "baby bottle", "image": "words/baby_bottle.png", "voice": "voices-$CA/$LOCALE/words/baby_bottle.$CA" }, { "description": "balance", "image": "words/balance.png", "voice": "voices-$CA/$LOCALE/words/balance.$CA" }, { "description": "blade", "image": "words/blade.png", "voice": "voices-$CA/$LOCALE/words/blade.$CA" }, { "description": "can", "image": "words/can.png", "voice": "voices-$CA/$LOCALE/words/can.$CA" }, { "description": "carafe", "image": "words/carafe.png", "voice": "voices-$CA/$LOCALE/words/carafe.$CA" }, { "description": "cauldron", "image": "words/cauldron.png", "voice": "voices-$CA/$LOCALE/words/cauldron.$CA" }, { "description": "chandelier", "image": "words/chandelier.png", "voice": "voices-$CA/$LOCALE/words/chandelier.$CA" }, { "description": "clock", "image": "words/clock.png", "voice": "voices-$CA/$LOCALE/words/clock.$CA" }, { "description": "curtain", "image": "words/curtain.png", "voice": "voices-$CA/$LOCALE/words/curtain.$CA" }, { "description": "dish", "image": "words/dish.png", "voice": "voices-$CA/$LOCALE/words/dish.$CA" }, { "description": "fork", "image": "words/fork.png", "voice": "voices-$CA/$LOCALE/words/fork.$CA" }, { "description": "glass", "image": "words/glass.png", "voice": "voices-$CA/$LOCALE/words/glass.$CA" }, { "description": "knife", "image": "words/knife.png", "voice": "voices-$CA/$LOCALE/words/knife.$CA" }, { "description": "lid", "image": "words/lid.png", "voice": "voices-$CA/$LOCALE/words/lid.$CA" }, { "description": "mop", "image": "words/mop.png", "voice": "voices-$CA/$LOCALE/words/mop.$CA" }, { "description": "mug", "image": "words/mug.png", "voice": "voices-$CA/$LOCALE/words/mug.$CA" }, { "description": "pan", "image": "words/pan.png", "voice": "voices-$CA/$LOCALE/words/pan.$CA" }, { "description": "plate", "image": "words/plate.png", "voice": "voices-$CA/$LOCALE/words/plate.$CA" }, { "description": "pot", "image": "words/pot.png", "voice": "voices-$CA/$LOCALE/words/pot.$CA" }, { "description": "scale", "image": "words/scale.png", "voice": "voices-$CA/$LOCALE/words/scale.$CA" }, { "description": "skimmer", "image": "words/skimmer.png", "voice": "voices-$CA/$LOCALE/words/skimmer.$CA" }, { "description": "spatula", "image": "words/spatula.png", "voice": "voices-$CA/$LOCALE/words/spatula.$CA" }, { "description": "sponge", "image": "words/sponge.png", "voice": "voices-$CA/$LOCALE/words/sponge.$CA" }, { "description": "spoon", "image": "words/spoon.png", "voice": "voices-$CA/$LOCALE/words/spoon.$CA" }, { "description": "strainer", "image": "words/strainer.png", "voice": "voices-$CA/$LOCALE/words/strainer.$CA" }, { "description": "tablecloth", "image": "words/tablecloth.png", "voice": "voices-$CA/$LOCALE/words/tablecloth.$CA" }, { "description": "tin", "image": "words/tin.png", "voice": "voices-$CA/$LOCALE/words/tin.$CA" }, { "description": "toaster", "image": "words/toaster.png", "voice": "voices-$CA/$LOCALE/words/toaster.$CA" }, { "description": "tongs", "image": "words/tongs.png", "voice": "voices-$CA/$LOCALE/words/tongs.$CA" }, { "description": "trash", "image": "words/trash.png", "voice": "voices-$CA/$LOCALE/words/trash.$CA" }, { "description": "tray", "image": "words/tray.png", "voice": "voices-$CA/$LOCALE/words/tray.$CA" }, { "description": "vase", "image": "words/vase.png", "voice": "voices-$CA/$LOCALE/words/vase.$CA" }, { "description": "whisk", "image": "words/whisk.png", "voice": "voices-$CA/$LOCALE/words/whisk.$CA" }, { "description": "alarmclock", "image": "words/alarmclock.png", "voice": "voices-$CA/$LOCALE/words/alarmclock.$CA" } ] }, { "type": "lesson", "name": "object", "content": [ { "description": "faucet", "image": "words/faucet.png", "voice": "voices-$CA/$LOCALE/words/faucet.$CA" }, { "description": "air horn", "image": "words/air_horn.png", "voice": "voices-$CA/$LOCALE/words/air_horn.$CA" }, { "description": "anchor", "image": "words/anchor.png", "voice": "voices-$CA/$LOCALE/words/anchor.$CA" }, { "description": "badge", "image": "words/badge.png", "voice": "voices-$CA/$LOCALE/words/badge.$CA" }, { "description": "bag", "image": "words/bag.png", "voice": "voices-$CA/$LOCALE/words/bag.$CA" }, { "description": "ball", "image": "words/ball.png", "voice": "voices-$CA/$LOCALE/words/ball.$CA" }, { "description": "soccer ball", "image": "words/ball_soccer.png", "voice": "voices-$CA/$LOCALE/words/ball_soccer.$CA" }, { "description": "bead", "image": "words/bead.png", "voice": "voices-$CA/$LOCALE/words/bead.$CA" }, { "description": "bell", "image": "words/bell.png", "voice": "voices-$CA/$LOCALE/words/bell.$CA" }, { "description": "block", "image": "words/block.png", "voice": "voices-$CA/$LOCALE/words/block.$CA" }, { "description": "board", "image": "words/board.png", "voice": "voices-$CA/$LOCALE/words/board.$CA" }, { "description": "bomb", "image": "words/bomb.png", "voice": "voices-$CA/$LOCALE/words/bomb.$CA" }, { "description": "book", "image": "words/book.png", "voice": "voices-$CA/$LOCALE/words/book.$CA" }, { "description": "box", "image": "words/box.png", "voice": "voices-$CA/$LOCALE/words/box.$CA" }, { "description": "bucket", "image": "words/bucket.png", "voice": "voices-$CA/$LOCALE/words/bucket.$CA" }, { "description": "bulb", "image": "words/bulb.png", "voice": "voices-$CA/$LOCALE/words/bulb.$CA" }, { "description": "camera", "image": "words/camera.png", "voice": "voices-$CA/$LOCALE/words/camera.$CA" }, { "description": "candle", "image": "words/candle.png", "voice": "voices-$CA/$LOCALE/words/candle.$CA" }, { "description": "cane", "image": "words/cane.png", "voice": "voices-$CA/$LOCALE/words/cane.$CA" }, { "description": "canon", "image": "words/canon.png", "voice": "voices-$CA/$LOCALE/words/canon.$CA" }, { "description": "card", "image": "words/card.png", "voice": "voices-$CA/$LOCALE/words/card.$CA" }, { "description": "cart", "image": "words/cart.png", "voice": "voices-$CA/$LOCALE/words/cart.$CA" }, { "description": "cash", "image": "words/cash.png", "voice": "voices-$CA/$LOCALE/words/cash.$CA" }, { "description": "chain", "image": "words/chain.png", "voice": "voices-$CA/$LOCALE/words/chain.$CA" }, { "description": "chalk", "image": "words/chalk.png", "voice": "voices-$CA/$LOCALE/words/chalk.$CA" }, { "description": "cigar", "image": "words/cigar.png", "voice": "voices-$CA/$LOCALE/words/cigar.$CA" }, { "description": "clay", "image": "words/clay.png", "voice": "voices-$CA/$LOCALE/words/clay.$CA" }, { "description": "cloth", "image": "words/cloth.png", "voice": "voices-$CA/$LOCALE/words/cloth.$CA" }, { "description": "clothes hanger", "image": "words/clothes_hanger.png", "voice": "voices-$CA/$LOCALE/words/clothes_hanger.$CA" }, { "description": "coin", "image": "words/coin.png", "voice": "voices-$CA/$LOCALE/words/coin.$CA" }, { "description": "comb", "image": "words/comb.png", "voice": "voices-$CA/$LOCALE/words/comb.$CA" }, { "description": "cone", "image": "words/cone.png", "voice": "voices-$CA/$LOCALE/words/cone.$CA" }, { "description": "cork", "image": "words/cork.png", "voice": "voices-$CA/$LOCALE/words/cork.$CA" }, { "description": "cross", "image": "words/cross.png", "voice": "voices-$CA/$LOCALE/words/cross.$CA" }, { "description": "crown", "image": "words/crown.png", "voice": "voices-$CA/$LOCALE/words/crown.$CA" }, { "description": "cube", "image": "words/cube.png", "voice": "voices-$CA/$LOCALE/words/cube.$CA" }, { "description": "tire", "image": "words/tire.png", "voice": "voices-$CA/$LOCALE/words/tire.$CA" }, { "description": "dart board", "image": "words/dart_board.png", "voice": "voices-$CA/$LOCALE/words/dart_board.$CA" }, { "description": "dishcloth", "image": "words/dishcloth.png", "voice": "voices-$CA/$LOCALE/words/dishcloth.$CA" }, { "description": "doll", "image": "words/doll.png", "voice": "voices-$CA/$LOCALE/words/doll.$CA" }, { "description": "domino", "image": "words/domino.png", "voice": "voices-$CA/$LOCALE/words/domino.$CA" }, { "description": "drum", "image": "words/drum.png", "voice": "voices-$CA/$LOCALE/words/drum.$CA" }, { "description": "eraser", "image": "words/eraser.png", "voice": "voices-$CA/$LOCALE/words/eraser.$CA" }, { "description": "fire extinguisher", "image": "words/fire_extinguisher.png", "voice": "voices-$CA/$LOCALE/words/fire_extinguisher.$CA" }, { "description": "flacon", "image": "words/flacon.png", "voice": "voices-$CA/$LOCALE/words/flacon.$CA" }, { "description": "flag", "image": "words/flag.png", "voice": "voices-$CA/$LOCALE/words/flag.$CA" }, { "description": "fluff", "image": "words/fluff.png", "voice": "voices-$CA/$LOCALE/words/fluff.$CA" }, { "description": "flute", "image": "words/flute.png", "voice": "voices-$CA/$LOCALE/words/flute.$CA" }, { "description": "game", "image": "words/game.png", "voice": "voices-$CA/$LOCALE/words/game.$CA" }, { "description": "gift", "image": "words/gift.png", "voice": "voices-$CA/$LOCALE/words/gift.$CA" }, { "description": "glasses", "image": "words/glasses.png", "voice": "voices-$CA/$LOCALE/words/glasses.$CA" }, { "description": "glue", "image": "words/glue.png", "voice": "voices-$CA/$LOCALE/words/glue.$CA" }, { "description": "grill", "image": "words/grill.png", "voice": "voices-$CA/$LOCALE/words/grill.$CA" }, { "description": "hair-dryer", "image": "words/hair_dryer.png", "voice": "voices-$CA/$LOCALE/words/hair_dryer.$CA" }, { "description": "handlebar", "image": "words/handlebar.png", "voice": "voices-$CA/$LOCALE/words/handlebar.$CA" }, { "description": "harp", "image": "words/harp.png", "voice": "voices-$CA/$LOCALE/words/harp.$CA" }, { "description": "hook", "image": "words/hook.png", "voice": "voices-$CA/$LOCALE/words/hook.$CA" }, { "description": "hose", "image": "words/hose.png", "voice": "voices-$CA/$LOCALE/words/hose.$CA" }, { "description": "ink", "image": "words/ink.png", "voice": "voices-$CA/$LOCALE/words/ink.$CA" }, { "description": "jewel", "image": "words/jewel.png", "voice": "voices-$CA/$LOCALE/words/jewel.$CA" }, { "description": "keel", "image": "words/keel.png", "voice": "voices-$CA/$LOCALE/words/keel.$CA" }, { "description": "keyboard", "image": "words/keyboard.png", "voice": "voices-$CA/$LOCALE/words/keyboard.$CA" }, { "description": "kite", "image": "words/kite.png", "voice": "voices-$CA/$LOCALE/words/kite.$CA" }, { "description": "knot", "image": "words/knot.png", "voice": "voices-$CA/$LOCALE/words/knot.$CA" }, { "description": "lasso", "image": "words/lasso.png", "voice": "voices-$CA/$LOCALE/words/lasso.$CA" }, { "description": "log", "image": "words/log.png", "voice": "voices-$CA/$LOCALE/words/log.$CA" }, { "description": "magnet", "image": "words/magnet.png", "voice": "voices-$CA/$LOCALE/words/magnet.$CA" }, { "description": "magnifying glass", "image": "words/magnifying_glass.png", "voice": "voices-$CA/$LOCALE/words/magnifying_glass.$CA" }, { "description": "mail", "image": "words/mail.png", "voice": "voices-$CA/$LOCALE/words/mail.$CA" }, { "description": "map", "image": "words/map.png", "voice": "voices-$CA/$LOCALE/words/map.$CA" }, { "description": "marble", "image": "words/marble.png", "voice": "voices-$CA/$LOCALE/words/marble.$CA" }, { "description": "mask", "image": "words/mask.png", "voice": "voices-$CA/$LOCALE/words/mask.$CA" }, { "description": "mast", "image": "words/mast.png", "voice": "voices-$CA/$LOCALE/words/mast.$CA" }, { "description": "match", "image": "words/match.png", "voice": "voices-$CA/$LOCALE/words/match.$CA" }, { "description": "medal", "image": "words/medal.png", "voice": "voices-$CA/$LOCALE/words/medal.$CA" }, { "description": "microphone", "image": "words/microphone.png", "voice": "voices-$CA/$LOCALE/words/microphone.$CA" }, { "description": "mirror", "image": "words/mirror.png", "voice": "voices-$CA/$LOCALE/words/mirror.$CA" }, { "description": "mixer", "image": "words/mixer.png", "voice": "voices-$CA/$LOCALE/words/mixer.$CA" }, { "description": "necklace", "image": "words/necklace.png", "voice": "voices-$CA/$LOCALE/words/necklace.$CA" }, { "description": "nest", "image": "words/nest.png", "voice": "voices-$CA/$LOCALE/words/nest.$CA" }, { "description": "net", "image": "words/net.png", "voice": "voices-$CA/$LOCALE/words/net.$CA" }, { "description": "newspaper", "image": "words/newspaper.png", "voice": "voices-$CA/$LOCALE/words/newspaper.$CA" }, { "description": "notebook", "image": "words/notebook.png", "voice": "voices-$CA/$LOCALE/words/notebook.$CA" }, { "description": "oar", "image": "words/oar.png", "voice": "voices-$CA/$LOCALE/words/oar.$CA" }, { "description": "pacifier", "image": "words/pacifier.png", "voice": "voices-$CA/$LOCALE/words/pacifier.$CA" }, { "description": "page", "image": "words/page.png", "voice": "voices-$CA/$LOCALE/words/page.$CA" }, { "description": "pair", "image": "words/pair.png", "voice": "voices-$CA/$LOCALE/words/pair.$CA" }, { "description": "paper", "image": "words/paper.png", "voice": "voices-$CA/$LOCALE/words/paper.$CA" }, { "description": "pearl", "image": "words/pearl.png", "voice": "voices-$CA/$LOCALE/words/pearl.$CA" }, { "description": "pen", "image": "words/pen.png", "voice": "voices-$CA/$LOCALE/words/pen.$CA" }, { "description": "pencil", "image": "words/pencil.png", "voice": "voices-$CA/$LOCALE/words/pencil.$CA" }, { "description": "phone", "image": "words/phone.png", "voice": "voices-$CA/$LOCALE/words/phone.$CA" }, { "description": "piano", "image": "words/piano.png", "voice": "voices-$CA/$LOCALE/words/piano.$CA" }, { "description": "picture", "image": "words/picture.png", "voice": "voices-$CA/$LOCALE/words/picture.$CA" }, { "description": "pill", "image": "words/pill.png", "voice": "voices-$CA/$LOCALE/words/pill.$CA" }, { "description": "pillow", "image": "words/pillow.png", "voice": "voices-$CA/$LOCALE/words/pillow.$CA" }, { "description": "pipe", "image": "words/pipe.png", "voice": "voices-$CA/$LOCALE/words/pipe.$CA" }, { "description": "pole", "image": "words/pole.png", "voice": "voices-$CA/$LOCALE/words/pole.$CA" }, { "description": "prize", "image": "words/prize.png", "voice": "voices-$CA/$LOCALE/words/prize.$CA" }, { "description": "radio", "image": "words/radio.png", "voice": "voices-$CA/$LOCALE/words/radio.$CA" }, { "description": "rag", "image": "words/rag.png", "voice": "voices-$CA/$LOCALE/words/rag.$CA" }, { "description": "razor", "image": "words/razor.png", "voice": "voices-$CA/$LOCALE/words/razor.$CA" }, { "description": "rifle", "image": "words/rifle.png", "voice": "voices-$CA/$LOCALE/words/rifle.$CA" }, { "description": "robot", "image": "words/robot.png", "voice": "voices-$CA/$LOCALE/words/robot.$CA" }, { "description": "rolling pin", "image": "words/rolling_pin.png", "voice": "voices-$CA/$LOCALE/words/rolling_pin.$CA" }, { "description": "rope", "image": "words/rope.png", "voice": "voices-$CA/$LOCALE/words/rope.$CA" }, { "description": "saddle", "image": "words/saddle.png", "voice": "voices-$CA/$LOCALE/words/saddle.$CA" }, { "description": "school bag", "image": "words/school_bag.png", "voice": "voices-$CA/$LOCALE/words/school_bag.$CA" }, { "description": "sign", "image": "words/sign.png", "voice": "voices-$CA/$LOCALE/words/sign.$CA" }, { "description": "sleigh", "image": "words/sleigh.png", "voice": "voices-$CA/$LOCALE/words/sleigh.$CA" }, { "description": "slide", "image": "words/slide.png", "voice": "voices-$CA/$LOCALE/words/slide.$CA" }, { "description": "slot", "image": "words/slot.png", "voice": "voices-$CA/$LOCALE/words/slot.$CA" }, { "description": "soap", "image": "words/soap.png", "voice": "voices-$CA/$LOCALE/words/soap.$CA" }, { "description": "spark", "image": "words/spark.png", "voice": "voices-$CA/$LOCALE/words/spark.$CA" }, { "description": "spinning top", "image": "words/spinning_top.png", "voice": "voices-$CA/$LOCALE/words/spinning_top.$CA" }, { "description": "spool", "image": "words/spool.png", "voice": "voices-$CA/$LOCALE/words/spool.$CA" }, { "description": "squirt", "image": "words/squirt.png", "voice": "voices-$CA/$LOCALE/words/squirt.$CA" }, { "description": "stack", "image": "words/stack.png", "voice": "voices-$CA/$LOCALE/words/stack.$CA" }, { "description": "stamp", "image": "words/stamp.png", "voice": "voices-$CA/$LOCALE/words/stamp.$CA" }, { "description": "straw", "image": "words/straw.png", "voice": "voices-$CA/$LOCALE/words/straw.$CA" }, { "description": "string", "image": "words/string.png", "voice": "voices-$CA/$LOCALE/words/string.$CA" }, { "description": "suitcase", "image": "words/suitcase.png", "voice": "voices-$CA/$LOCALE/words/suitcase.$CA" }, { "description": "tag", "image": "words/tag.png", "voice": "voices-$CA/$LOCALE/words/tag.$CA" }, { "description": "teddy", "image": "words/teddy.png", "voice": "voices-$CA/$LOCALE/words/teddy.$CA" }, { "description": "thread", "image": "words/thread.png", "voice": "voices-$CA/$LOCALE/words/thread.$CA" }, { "description": "ticket", "image": "words/ticket.png", "voice": "voices-$CA/$LOCALE/words/ticket.$CA" }, { "description": "tissue", "image": "words/tissue.png", "voice": "voices-$CA/$LOCALE/words/tissue.$CA" }, { "description": "torch", "image": "words/torch.png", "voice": "voices-$CA/$LOCALE/words/torch.$CA" }, { "description": "towel", "image": "words/towel.png", "voice": "voices-$CA/$LOCALE/words/towel.$CA" }, { "description": "toy", "image": "words/toy.png", "voice": "voices-$CA/$LOCALE/words/toy.$CA" }, { "description": "trap", "image": "words/trap.png", "voice": "voices-$CA/$LOCALE/words/trap.$CA" }, { "description": "tube", "image": "words/tube.png", "voice": "voices-$CA/$LOCALE/words/tube.$CA" }, { "description": "umbrella", "image": "words/umbrella.png", "voice": "voices-$CA/$LOCALE/words/umbrella.$CA" }, { "description": "watch", "image": "words/watch.png", "voice": "voices-$CA/$LOCALE/words/watch.$CA" }, { "description": "wheel", "image": "words/wheel.png", "voice": "voices-$CA/$LOCALE/words/wheel.$CA" }, { "description": "wig", "image": "words/wig.png", "voice": "voices-$CA/$LOCALE/words/wig.$CA" }, { "description": "wood", "image": "words/wood.png", "voice": "voices-$CA/$LOCALE/words/wood.$CA" }, { "description": "wreath", "image": "words/wreath.png", "voice": "voices-$CA/$LOCALE/words/wreath.$CA" }, { "description": "ball of yarn", "image": "words/ball_of_yarn.png", "voice": "voices-$CA/$LOCALE/words/ball_of_yarn.$CA" }, { "description": "christmas", "image": "words/christmas.png", "voice": "voices-$CA/$LOCALE/words/christmas.$CA" }, { "description": "foam", "image": "words/foam.png", "voice": "voices-$CA/$LOCALE/words/foam.$CA" }, { "description": "guignol", "image": "words/guignol.png", "voice": "voices-$CA/$LOCALE/words/guignol.$CA" }, { "description": "movie", "image": "words/movie.png", "voice": "voices-$CA/$LOCALE/words/movie.$CA" }, { "description": "music", "image": "words/music.png", "voice": "voices-$CA/$LOCALE/words/music.$CA" }, { "description": "price", "image": "words/price.png", "voice": "voices-$CA/$LOCALE/words/price.$CA" }, { "description": "dot", "image": "words/dot.png", "voice": "voices-$CA/$LOCALE/words/dot.$CA" } ] }, { "type": "lesson", "name": "tool", "content": [ { "description": "ax", "image": "words/ax.png", "voice": "voices-$CA/$LOCALE/words/ax.$CA" }, { "description": "bolt", "image": "words/bolt.png", "voice": "voices-$CA/$LOCALE/words/bolt.$CA" }, { "description": "brick", "image": "words/brick.png", "voice": "voices-$CA/$LOCALE/words/brick.$CA" }, { "description": "brush", "image": "words/brush.png", "voice": "voices-$CA/$LOCALE/words/brush.$CA" }, { "description": "flash", "image": "words/flash.png", "voice": "voices-$CA/$LOCALE/words/flash.$CA" }, { "description": "hammer", "image": "words/hammer.png", "voice": "voices-$CA/$LOCALE/words/hammer.$CA" }, { "description": "mower", "image": "words/mower.png", "voice": "voices-$CA/$LOCALE/words/mower.$CA" }, { "description": "needle", "image": "words/needle.png", "voice": "voices-$CA/$LOCALE/words/needle.$CA" }, { "description": "pliers", "image": "words/pliers.png", "voice": "voices-$CA/$LOCALE/words/pliers.$CA" }, { "description": "rake", "image": "words/rake.png", "voice": "voices-$CA/$LOCALE/words/rake.$CA" }, { "description": "scissors", "image": "words/scissors.png", "voice": "voices-$CA/$LOCALE/words/scissors.$CA" }, { "description": "screw", "image": "words/screw.png", "voice": "voices-$CA/$LOCALE/words/screw.$CA" }, { "description": "screwdriver", "image": "words/screwdriver.png", "voice": "voices-$CA/$LOCALE/words/screwdriver.$CA" }, { "description": "shovel", "image": "words/shovel.png", "voice": "voices-$CA/$LOCALE/words/shovel.$CA" }, { "description": "spade", "image": "words/spade.png", "voice": "voices-$CA/$LOCALE/words/spade.$CA" }, { "description": "spear", "image": "words/spear.png", "voice": "voices-$CA/$LOCALE/words/spear.$CA" }, { "description": "tape measure", "image": "words/tape_measure.png", "voice": "voices-$CA/$LOCALE/words/tape_measure.$CA" }, { "description": "tool", "image": "words/tool.png", "voice": "voices-$CA/$LOCALE/words/tool.$CA" }, { "description": "wedge", "image": "words/wedge.png", "voice": "voices-$CA/$LOCALE/words/wedge.$CA" }, { "description": "wrench", "image": "words/wrench.png", "voice": "voices-$CA/$LOCALE/words/wrench.$CA" }, { "description": "trip", "image": "words/trip.png", "voice": "voices-$CA/$LOCALE/words/trip.$CA" }, { "description": "bike", "image": "words/bike.png", "voice": "voices-$CA/$LOCALE/words/bike.$CA" }, { "description": "boat", "image": "words/boat.png", "voice": "voices-$CA/$LOCALE/words/boat.$CA" }, { "description": "bus", "image": "words/bus.png", "voice": "voices-$CA/$LOCALE/words/bus.$CA" }, { "description": "canoe", "image": "words/canoe.png", "voice": "voices-$CA/$LOCALE/words/canoe.$CA" }, { "description": "car", "image": "words/car.png", "voice": "voices-$CA/$LOCALE/words/car.$CA" }, { "description": "engine", "image": "words/engine.png", "voice": "voices-$CA/$LOCALE/words/engine.$CA" }, { "description": "motorcycle", "image": "words/motorcycle.png", "voice": "voices-$CA/$LOCALE/words/motorcycle.$CA" }, { "description": "parachute", "image": "words/parachute.png", "voice": "voices-$CA/$LOCALE/words/parachute.$CA" }, { "description": "pedal", "image": "words/pedal.png", "voice": "voices-$CA/$LOCALE/words/pedal.$CA" }, { "description": "plane", "image": "words/plane.png", "voice": "voices-$CA/$LOCALE/words/plane.$CA" }, { "description": "porthole", "image": "words/porthole.png", "voice": "voices-$CA/$LOCALE/words/porthole.$CA" }, { "description": "raft", "image": "words/raft.png", "voice": "voices-$CA/$LOCALE/words/raft.$CA" }, { "description": "rocket", "image": "words/rocket.png", "voice": "voices-$CA/$LOCALE/words/rocket.$CA" }, { "description": "sail", "image": "words/sail.png", "voice": "voices-$CA/$LOCALE/words/sail.$CA" }, { "description": "ship", "image": "words/ship.png", "voice": "voices-$CA/$LOCALE/words/ship.$CA" }, { "description": "sled", "image": "words/sled.png", "voice": "voices-$CA/$LOCALE/words/sled.$CA" }, { "description": "taxi", "image": "words/taxi.png", "voice": "voices-$CA/$LOCALE/words/taxi.$CA" }, { "description": "train", "image": "words/train.png", "voice": "voices-$CA/$LOCALE/words/train.$CA" }, { "description": "truck", "image": "words/truck.png", "voice": "voices-$CA/$LOCALE/words/truck.$CA" }, { "description": "van", "image": "words/van.png", "voice": "voices-$CA/$LOCALE/words/van.$CA" } ] } ] } ] diff --git a/src/activities/lang/spell_it.js b/src/activities/lang/spell_it.js new file mode 100644 index 000000000..dc34ef344 --- /dev/null +++ b/src/activities/lang/spell_it.js @@ -0,0 +1,173 @@ +/* GCompris - spell_it.js +* +* Copyright (C) Siddhesh suthar (Qt Quick port) +* +* Authors: +* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) +* Holger Kaelberer (Qt Quick port of imageid) +* Siddhesh suthar (Qt Quick port) +* Bruno Coudoin (Integration Lang dataset) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, see . +*/ +.pragma library +.import QtQuick 2.0 as Quick +.import GCompris 1.0 as GCompris +.import "qrc:/gcompris/src/core/core.js" as Core +.import "qrc:/gcompris/src/activities/lang/lang_api.js" as Lang + +var spellItems; +var wordList; +var remainingWords + +// @return true if the quiz was ran +function init(loadedItems_, wordList_, mode_) { + spellItems = loadedItems_ + wordList = wordList_ + spellItems.answer.forceActiveFocus() + initLevel() + return true +} + +function initLevel() { + remainingWords = wordList.slice(); + Core.shuffle(remainingWords) + + spellItems.score.currentSubLevel = 0 + spellItems.score.numberOfSubLevels = wordList.length + + /* populate VirtualKeyboard for mobile: + * 1. for < 10 letters print them all in the same row + * 2. for > 10 letters create 3 rows with equal amount of keys per row + * if possible, otherwise more keys in the upper rows + */ + // first generate a map of needed letters + var letters = []; + for (var i = 0; i < wordList.length; i++) { + var currentWord = wordList[i].translatedTxt; + for (var j = 0; j < currentWord.length; j++) { + var letter = currentWord.charAt(j); + + if(letters.indexOf(letter) === -1) + letters.push(currentWord.charAt(j)); + } + } + letters.sort(); + // generate layout from letter map + var layout = []; + var row = 0; + var offset = 0; + while (offset < letters.length-1) { + var cols = letters.length <= 10 + ? letters.length + : (Math.ceil((letters.length-offset) / (3 - row))); + layout[row] = new Array(); + for (var j = 0; j < cols; j++) + layout[row][j] = { label: letters[j+offset] }; + offset += j; + row++; + } + layout[row-1].push({ label: spellItems.keyboard.backspace }); + spellItems.keyboard.layout = layout; + + initSubLevel() +} + +function initSubLevel() { + spellItems.score.currentSubLevel++ + spellItems.goodWord = wordList[spellItems.score.currentSubLevel - 1] + spellItems.wordImage.changeSource("qrc:/gcompris/data/" + + spellItems.goodWord.image) + spellItems.hintText.changeHint(spellItems.goodWord.translatedTxt[0]) + spellItems.hintText.visible = true + spellItems.answer.text = "" + spellItems.maximumLengthAnswer = spellItems.goodWord.translatedTxt.length + 1 +} + +function nextSubLevel() { + if(spellItems.score.currentSubLevel == spellItems.score.numberOfSubLevels ) { + spellItems.bonus.good("smiley") + } else { + initSubLevel(); + } +} + +function checkAnswer(answer_) { + if(spellItems.goodWord.translatedTxt == answer_) { + nextSubLevel() + return true + } + else { + badWordSelected(spellItems.score.currentSubLevel - 1, answer_) + } +} + +// Append to the front of queue of words for the sublevel the error +function badWordSelected(wordIndex, answer) { + if (remainingWords[0] != wordIndex) + remainingWords.unshift(wordIndex); + + provideHint(answer) +} + +//function to construct hint based on the answer entered by user +function provideHint(answer_) { + var answer = answer_ + var hint = "" + var firstIncorrectIndex = 0 + + for (var i=0 ; i< spellItems.goodWord.translatedTxt.length; i++) { + var goodChar = spellItems.goodWord.translatedTxt[i] + + //skipping hint if the suggestion is a space + if( goodChar == " ") { + hint = hint + " " + continue + } + + if( answer[i] == goodChar) { + hint = hint + goodChar + } else { + if(firstIncorrectIndex == 0) { + hint = hint + goodChar + firstIncorrectIndex = i + } else { + hint = hint + "." + } + } + + } + spellItems.hintText.changeHint(hint) + spellItems.hintText.visible = true +} + +// to handle virtual key board key press events +function processKeyPress(text_){ + var answer = spellItems.answer + var text = text_ + if ( text == spellItems.keyboard.backspace) { + backspace(answer) + return + } + answer.insert(answer.length,text) +} + +function backspace(answer) { + answer.text = answer.text.slice(0, -1) + if(answer.text.length === 0) { + answer.text = "" + } else { + checkAnswer(answer.text) + } +} diff --git a/src/activities/louis-braille/ActivityInfo.qml b/src/activities/louis-braille/ActivityInfo.qml index df1968be6..e95e7559b 100644 --- a/src/activities/louis-braille/ActivityInfo.qml +++ b/src/activities/louis-braille/ActivityInfo.qml @@ -1,35 +1,35 @@ /* GCompris - ActivityInfo.qml * * Copyright (C) 2015 Arkit Vora * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import GCompris 1.0 ActivityInfo { name: "louis-braille/LouisBraille.qml" difficulty: 4 icon: "louis-braille/resource/louis.png" author: "Arkit Vora <arkitvora123@gmail.com>" demo: true title: qsTr("The History of Louis Braille") description: qsTr("Review the major dates of the inventor of the Braille System") //intro: "Discover the history behind Louis Braille." goal: "" prerequisite: "" - manual: qsTr("Read the history of Louis Braille, his biography and discovery of the Braille system. -Click on the previous and next buttons to move to the respective story page. At the end, arrange the sequence in the chronological order.") - credit: qsTr("Louis Braille Video: http://www.youtube.com/watch?v=9bdfC2j_4x4") + manual: qsTr("Read the history of Louis Braille, his biography and discovery of the Braille system.) + Click on the previous and next buttons to move to the respective story page. At the end, arrange the sequence in the chronological order.") + credit: qsTr("Louis Braille Video: < http:\/\/www.youtube.com/watch?v=9bdfC2j_4x4 >") section: "discovery braille" } diff --git a/tools/menus/melody.qml b/src/activities/melody/ActivityInfo.qml similarity index 69% rename from tools/menus/melody.qml rename to src/activities/melody/ActivityInfo.qml index 30d52baab..eccd5aee9 100644 --- a/tools/menus/melody.qml +++ b/src/activities/melody/ActivityInfo.qml @@ -1,33 +1,34 @@ /* GCompris - ActivityInfo.qml * - * Copyright (C) 2015 Your Name + * Copyright (C) 2015 Bruno Coudoin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import GCompris 1.0 ActivityInfo { name: "melody/Melody.qml" difficulty: 2 icon: "melody/melody.svg" - author: "Jose JORGE <jjorge@free.fr>" - demo: true + author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" + demo: false title: qsTr("Melody") - description: qsTr("Repeat a melody") + description: qsTr("Reproduce a sound sequence") +// intro: "Listen to the sound sequence played, and reproduce it by clicking on the xylophone's bars" goal: qsTr("Ear-training activity") prerequisite: qsTr("Move and click the mouse") - manual: qsTr("Listen to the sound sequence played, and repeat it by clicking on the elements. You can listen again by clicking on the repeat button.") + manual: qsTr("Listen to the sound sequence played, and repeat it by clicking on the xylophone's bars. You can listen again by clicking on the repeat button.") credit: "" - section: "/discovery/sound_group" + section: "discovery memory music" } diff --git a/src/activities/melody/CMakeLists.txt b/src/activities/melody/CMakeLists.txt new file mode 100644 index 000000000..c02ebecb3 --- /dev/null +++ b/src/activities/melody/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/melody *.qml *.svg *.js resource/*) diff --git a/src/activities/melody/Melody.qml b/src/activities/melody/Melody.qml new file mode 100644 index 000000000..07beb605e --- /dev/null +++ b/src/activities/melody/Melody.qml @@ -0,0 +1,237 @@ +/* GCompris - melody.qml + * + * Copyright (C) 2015 Bruno Coudoin + * + * Authors: + * Jose JORGE (GTK+ version) + * Bruno Coudoin (Qt Quick port) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.1 +import GCompris 1.0 + +import "../../core" + +ActivityBase { + id: activity + + onStart: focus = true + onStop: {} + + pageComponent: Image { + id: background + anchors.fill: parent + source: items.url + 'xylofon_background.svg' + sourceSize.width: parent.width + fillMode: Image.PreserveAspectCrop + + signal start + signal stop + + Component.onCompleted: { + activity.start.connect(start) + activity.stop.connect(stop) + } + + // Add here the QML items you need to access in javascript + QtObject { + id: items + property string url: "qrc:/gcompris/src/activities/melody/resource/" + property var question + property var questionToPlay + property var answer + property alias questionInterval: questionPlayer.interval + property int numberOfLevel: 10 + } + + onStart: { + bar.level = 1 + score.numberOfSubLevels = 5 + score.currentSubLevel = 1 + initLevel() + } + + onStop: { + questionPlayer.stop() + } + + Image { + id: xylofon + anchors { + fill: parent + margins: 10 * ApplicationInfo.ratio + } + source: items.url + 'xylofon.svg' + sourceSize.width: parent.width * 0.7 + fillMode: Image.PreserveAspectFit + } + + Repeater { + id: parts + model: 4 + Image { + id: part + parent: xylofon + source: items.url + 'xylofon_part' + (index + 1) + '.svg' + rotation: - 80 + anchors.horizontalCenter: xylofon.horizontalCenter + anchors.horizontalCenterOffset: (- xylofon.paintedWidth) * 0.3 + xylofon.paintedWidth * index * 0.22 + anchors.verticalCenter: xylofon.verticalCenter + anchors.verticalCenterOffset: - xylofon.paintedHeight * 0.1 + sourceSize.width: xylofon.paintedWidth * 0.5 + fillMode: Image.PreserveAspectFit + + property alias anim: anim + + SequentialAnimation { + id: anim + NumberAnimation { + target: part + property: "scale" + from: 1; to: 0.95 + duration: 150 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: part + property: "scale" + from: 0.95; to: 1 + duration: 150 + easing.type: Easing.OutElastic + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + anim.start() + background.playNote(index) + items.answer.push(index) + background.checkAnswer() + } + } + } + } + + function playNote(index) { + activity.audioEffects.play(items.url + + 'xylofon_son' + (index + 1) + '.ogg') + } + + Timer { + id: questionPlayer + onTriggered: { + var partIndex = items.questionToPlay.shift() + if(partIndex !== undefined) { + parts.itemAt(partIndex).anim.start() + background.playNote(partIndex) + start() + } + } + } + + DialogHelp { + id: dialogHelp + onClose: home() + } + + Bar { + id: bar + content: BarEnumContent { value: help | home | level | repeat } + onHelpClicked: { + displayDialog(dialogHelp) + } + onPreviousLevelClicked: { + score.currentSubLevel = 1 + if(bar.level == 1) { + bar.level = items.numberOfLevel + } else { + bar.level-- + } + initLevel(); + } + onNextLevelClicked: parent.nextLevel() + onHomeClicked: activity.home() + onRepeatClicked: parent.repeat() + } + + Bonus { + id: bonus + onWin: { + parent.nextSubLevel() + parent.repeat() + } + onLoose: parent.repeat() + } + + Score { + id: score + anchors.bottom: undefined + anchors.right: parent.right + anchors.rightMargin: 10 * ApplicationInfo.ratio + anchors.top: parent.top + } + + function initLevel() { + items.question = [] + questionPlayer.stop() + + var numberOfParts = 4 + if(bar.level < 3) + numberOfParts = 2 + else if(bar.level < 5) + numberOfParts = 3 + + for(var i = 0; i < bar.level + 2; ++i) { + items.question.push(Math.floor(Math.random() * numberOfParts)) + } + items.questionInterval = 1000 - Math.min(500, 100 * bar.level) + items.answer = [] + } + + function nextSubLevel() { + if(score.currentSubLevel < score.numberOfSubLevels) { + score.currentSubLevel++ + initLevel() + return + } + nextLevel() + } + + function nextLevel() { + score.currentSubLevel = 1 + if(items.numberOfLevel === bar.level ) { + bar.level = 1 + } else { + bar.level++ + } + initLevel(); + } + + function repeat() { + activity.audioEffects.play(items.url + 'xylofon_melody.ogg') + items.questionToPlay = items.question.slice() + items.answer = [] + questionPlayer.start() + } + + function checkAnswer() { + if(items.answer.join() == items.question.join()) + bonus.good('lion') + else if(items.answer.length >= items.question.length) + bonus.bad('lion') + } + } +} diff --git a/tools/menus/resource/melody.svg b/src/activities/melody/melody.svg similarity index 100% rename from tools/menus/resource/melody.svg rename to src/activities/melody/melody.svg diff --git a/src/activities/melody/resource/xylofon.svg b/src/activities/melody/resource/xylofon.svg new file mode 100644 index 000000000..a856bc8ee --- /dev/null +++ b/src/activities/melody/resource/xylofon.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_background.svg b/src/activities/melody/resource/xylofon_background.svg new file mode 100644 index 000000000..a811770b1 --- /dev/null +++ b/src/activities/melody/resource/xylofon_background.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_melody.ogg b/src/activities/melody/resource/xylofon_melody.ogg new file mode 100644 index 000000000..3dd9c7210 Binary files /dev/null and b/src/activities/melody/resource/xylofon_melody.ogg differ diff --git a/src/activities/melody/resource/xylofon_part1.svg b/src/activities/melody/resource/xylofon_part1.svg new file mode 100644 index 000000000..1d3140671 --- /dev/null +++ b/src/activities/melody/resource/xylofon_part1.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_part2.svg b/src/activities/melody/resource/xylofon_part2.svg new file mode 100644 index 000000000..d31c39156 --- /dev/null +++ b/src/activities/melody/resource/xylofon_part2.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_part3.svg b/src/activities/melody/resource/xylofon_part3.svg new file mode 100644 index 000000000..434b13e2b --- /dev/null +++ b/src/activities/melody/resource/xylofon_part3.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_part4.svg b/src/activities/melody/resource/xylofon_part4.svg new file mode 100644 index 000000000..5262ae3e6 --- /dev/null +++ b/src/activities/melody/resource/xylofon_part4.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/activities/melody/resource/xylofon_son1.ogg b/src/activities/melody/resource/xylofon_son1.ogg new file mode 100644 index 000000000..eaed42153 Binary files /dev/null and b/src/activities/melody/resource/xylofon_son1.ogg differ diff --git a/src/activities/melody/resource/xylofon_son2.ogg b/src/activities/melody/resource/xylofon_son2.ogg new file mode 100644 index 000000000..38fb0c1fd Binary files /dev/null and b/src/activities/melody/resource/xylofon_son2.ogg differ diff --git a/src/activities/melody/resource/xylofon_son3.ogg b/src/activities/melody/resource/xylofon_son3.ogg new file mode 100644 index 000000000..17548cb88 Binary files /dev/null and b/src/activities/melody/resource/xylofon_son3.ogg differ diff --git a/src/activities/melody/resource/xylofon_son4.ogg b/src/activities/melody/resource/xylofon_son4.ogg new file mode 100644 index 000000000..3a92c17fd Binary files /dev/null and b/src/activities/melody/resource/xylofon_son4.ogg differ diff --git a/src/activities/missing-letter/MissingLetter.qml b/src/activities/missing-letter/MissingLetter.qml index 1d580a39f..22aac3249 100644 --- a/src/activities/missing-letter/MissingLetter.qml +++ b/src/activities/missing-letter/MissingLetter.qml @@ -1,239 +1,335 @@ /* GCompris - missing-letter.qml * * Copyright (C) 2014 "Amit Tomar" * * Authors: * "Pascal Georges" (GTK+ version) * "Amit Tomar" (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import GCompris 1.0 import "../../core" import "missing-letter.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} + // When going on configuration, it steals the focus and re set it to the activity. + // We need to set it back to the textinput item in order to have key events. + onFocusChanged: { + if(focus) { + Activity.focusTextInput() + } + } pageComponent: Image { id: background source: Activity.url + "background.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop + readonly property string wordsResource: "data2/words/words.rcc" + property bool englishFallback: false + property bool downloadWordsNeeded: false signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main + property alias background: background property alias bar: bar property alias bonus: bonus + property alias score: score property alias questionImage: questionImage property alias questionText: questionText property alias answers: answers - property alias currentQuestionNumberText : currentQuestionNumberText property GCAudio audioVoices: activity.audioVoices + property alias englishFallbackDialog: englishFallbackDialog + property alias parser: parser + property string answer + property alias textinput: textinput + } + + function handleResourceRegistered(resource) + { + if (resource == wordsResource) + Activity.start(); + } + + onStart: { + Activity.init(items) + Activity.focusTextInput() + + // check for words.rcc: + if (DownloadManager.isDataRegistered("words")) { + // words.rcc is already registered -> start right away + Activity.start(); + } else if(DownloadManager.haveLocalResource(wordsResource)) { + // words.rcc is there -> register old file first + if (DownloadManager.registerResource(wordsResource)) + Activity.start(items); + else // could not register the old data -> react to a possible update + DownloadManager.resourceRegistered.connect(handleResourceRegistered); + // then try to update in the background + DownloadManager.updateResource(wordsResource); + } else { + // words.rcc has not been downloaded yet -> ask for download + downloadWordsNeeded = true + } } + onStop: { + DownloadManager.resourceRegistered.disconnect(handleResourceRegistered); + Activity.stop() + } + + TextInput { + // Helper element to capture composed key events like french ô which + // are not available via Keys.onPressed() on linux. Must be + // disabled on mobile! + id: textinput + anchors.centerIn: background + enabled: !ApplicationInfo.isMobile + focus: true + visible: false - onStart: { Activity.start(items) } - onStop: { Activity.stop() } + onTextChanged: { + if (text != '') { + var typedText = text + var answerText = Activity.getCurrentQuestion().answer + if(ApplicationSettings.fontCapitalization === Font.AllUppercase) + typedText = text.toLocaleUpperCase() + else if(ApplicationSettings.fontCapitalization === Font.AllLowercase) + typedText = text.toLocaleLowerCase() - // Option holder for buttons shown on the left of screen + if(typedText === answerText) { + questionAnim.start() + Activity.showAnswer() + } + text = ''; + } + } + } + + // Buttons with possible answers shown on the left of screen Column { id: buttonHolder - property bool buttonHolderMouseArea : true spacing: 10 * ApplicationInfo.ratio x: holder.x - width - 10 * ApplicationInfo.ratio - y: 30 + y: holder.y add: Transition { - NumberAnimation { properties: "y"; from: 10; duration: 500 } + NumberAnimation { properties: "y"; from: holder.y; duration: 500 } } Repeater { id: answers AnswerButton { width: 120 * ApplicationInfo.ratio - height: 80 * ApplicationInfo.ratio + height: (holder.height + - buttonHolder.spacing * answers.model.length) / answers.model.length textLabel: modelData - isCorrectAnswer: modelData === Activity.getCorrectAnswer() - onCorrectlyPressed: Activity.answerPressed(modelData) - onPressed: { - Activity.playLetter(modelData) - if(modelData === Activity.getCorrectAnswer()) Activity.showAnswer() - } + isCorrectAnswer: modelData === items.answer + onCorrectlyPressed: questionAnim.start() + onPressed: modelData == items.answer ? Activity.showAnswer() : '' } } } // Picture holder for different images being shown Rectangle { id: holder width: Math.max(questionImage.width * 1.1, questionImage.height * 1.1) height: questionTextBg.y + questionTextBg.height x: (background.width - width - 130 * ApplicationInfo.ratio) / 2 + 130 * ApplicationInfo.ratio y: 20 color: "black" radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#80FFFFFF" } GradientStop { position: 0.9; color: "#80EEEEEE" } GradientStop { position: 1.0; color: "#80AAAAAA" } } Item { id: spacer height: 20 } Image { id: questionImage anchors.horizontalCenter: holder.horizontalCenter anchors.top: spacer.bottom width: Math.min((background.width - 120 * ApplicationInfo.ratio) * 0.7, (background.height - 100 * ApplicationInfo.ratio) * 0.7) height: width } Rectangle { id: questionTextBg width: holder.width - height: questionText.height * 1.5 + height: questionText.height * 1.1 anchors.horizontalCenter: holder.horizontalCenter - anchors.margins: 20 anchors.top: questionImage.bottom radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } - } - GCText { - id: questionText - anchors { - horizontalCenter: questionTextBg.horizontalCenter - verticalCenter: questionTextBg.verticalCenter - } - style: Text.Outline; styleColor: "black" - color: "white" - fontSize: largeSize - - states: [ - State { - name: "question" - PropertyChanges { + GCText { + id: questionText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + style: Text.Outline + styleColor: "black" + color: "white" + fontSize: largeSize + wrapMode: Text.WordWrap + width: holder.width + + SequentialAnimation { + id: questionAnim + NumberAnimation { target: questionText - scale: 1.0 - rotation: 0 + property: 'scale' + to: 1.05 + duration: 500 + easing.type: Easing.OutQuad } - }, - State { - name: "answer" - PropertyChanges { + NumberAnimation { target: questionText - scale: 1.6 + property: 'scale' + to: 0.95 + duration: 1000 + easing.type: Easing.OutQuad + } + NumberAnimation { + target: questionText + property: 'scale' + to: 1.0 + duration: 500 + easing.type: Easing.OutQuad + } + ScriptAction { + script: Activity.nextSubLevel() } } - ] - - Behavior on scale { NumberAnimation { duration: 200 } } + } } - } - // Counter of progress within this level - Rectangle - { - width: 130 * 0.7 * ApplicationInfo.ratio - height: 70 * ApplicationInfo.ratio - anchors { - right: parent.right - bottom: parent.bottom - margins: 10 - } - radius: 10 - border.width: 3 - border.color: "black" - gradient: Gradient { - GradientStop { position: 0.0; color: "#fdf1aa" } - GradientStop { position: 0.1; color: "#fcec89" } - GradientStop { position: 0.4; color: "#f8d600" } - GradientStop { position: 1.0; color: "#f8d600" } - } + } - GCText { - id: currentQuestionNumberText - anchors.centerIn: parent - fontSize: largeSize - style: Text.Outline; styleColor: "white" - color: "black" - } + Score { + id: score + anchors.bottom: undefined + anchors.bottomMargin: 10 * ApplicationInfo.ratio + anchors.right: parent.right + anchors.rightMargin: 10 * ApplicationInfo.ratio + anchors.top: parent.top } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar - content: BarEnumContent { value: help | home | level } + content: BarEnumContent { value: help | home | level | repeat } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() + onRepeatClicked: Activity.playCurrentWord() } Bonus { id: bonus - Component.onCompleted: - { - win.connect(Activity.correctOptionPressed) - loose.connect(Activity.wrongOptionPressed) + Component.onCompleted: win.connect(Activity.nextLevel) + } + + JsonParser { + id: parser + onError: console.error("missing letter: Error parsing json: " + msg); + } + + Loader { + id: downloadWordsDialog + sourceComponent: GCDialog { + parent: activity.main + message: qsTr("The images for this activity are not yet installed.") + button1Text: qsTr("Download the images") + onClose: background.downloadWordsNeeded = false + onButton1Hit: { + DownloadManager.resourceRegistered.connect(handleResourceRegistered); + DownloadManager.downloadResource(wordsResource) + var downloadDialog = Core.showDownloadDialog(activity, {}); + } } + anchors.fill: parent + focus: true + active: background.downloadWordsNeeded + onStatusChanged: if (status == Loader.Ready) item.start() } + + Loader { + id: englishFallbackDialog + sourceComponent: GCDialog { + parent: activity.main + message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + + qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("http://l10n.kde.org/") + + "

" + + qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") + onClose: background.englishFallback = false + } + anchors.fill: parent + focus: true + active: background.englishFallback + onStatusChanged: if (status == Loader.Ready) item.start() + } + } } diff --git a/src/activities/missing-letter/missing-letter.js b/src/activities/missing-letter/missing-letter.js index 5ed0bba1d..0b7f65c37 100644 --- a/src/activities/missing-letter/missing-letter.js +++ b/src/activities/missing-letter/missing-letter.js @@ -1,260 +1,258 @@ /* GCompris - missing-letter.js * * Copyright (C) 2014 "Amit Tomar" * * Authors: * "Pascal Georges" (GTK+ version) * "Amit Tomar" (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ .pragma library .import QtQuick 2.0 as Quick .import "qrc:/gcompris/src/core/core.js" as Core .import GCompris 1.0 as GCompris //for ApplicationInfo +.import "qrc:/gcompris/src/activities/lang/lang_api.js" as Lang var url = "qrc:/gcompris/src/activities/missing-letter/resource/" - -var questionsStatic = - [ - [ - // Level 1 - { - questionString: "c_r", - answerString : "car", - choiceString : "atr", - pictureSource : url + "car.png", - }, - { - questionString: "_og", - answerString : "dog", - choiceString : "dws", - pictureSource : url + "dog.png", - }, - { - questionString: "_pple", - answerString : "apple", - choiceString : "back", - pictureSource : url + "apple.png", - }, - - { - questionString: "bal_", - answerString : "ball", - choiceString : "hlt", - pictureSource : url + "ball.png", - } - ], - [ - // Level 2 - { - questionString: "be_", - answerString : "bed", - choiceString : "dfg", - pictureSource : url + "bed.png", - }, - { - questionString: "_ake", - answerString : "cake", - choiceString : "lxc", - pictureSource : url + "cake.png", - }, - { - questionString: "ba_", - answerString : "bag", - choiceString : "qlg", - pictureSource : url + "bag.png", - }, - { - questionString: "f_sh", - answerString : "fish", - choiceString : "epi", - pictureSource : url + "fish.png", - } - ], - [ - // Level 3 - { - questionString: "bana_a", - answerString : "banana", - choiceString : "bakn", - pictureSource : url + "banana.png", - }, - { - questionString: "bottl_", - answerString : "bottle", - choiceString : "degw", - pictureSource : url + "bottle.png", - }, - { - questionString: "_ouse", - answerString : "house", - choiceString : "khpz", - pictureSource : url + "house.png", - }, - { - questionString: "_lane", - answerString : "plane", - choiceString : "lmpo", - pictureSource : url + "plane.png", - } - ] - ] +var langUrl = "qrc:/gcompris/src/activities/lang/resource/"; var items var currentLevel var numberOfLevel -var currentQuestionNumber var questions var dataset +var lessons + +// Do not propose these letter in the choices +var ignoreLetters = '[ ,;:\u0027]' -function start(items_) +function init(items_) { items = items_ +} + +function start() +{ currentLevel = 0 - questions = questionsStatic - createLastLevel() + + var locale = GCompris.ApplicationInfo.getVoicesLocale(GCompris.ApplicationSettings.locale) + + // register the voices for the locale + GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(locale)) + + dataset = Lang.load(items.parser, langUrl, "words.json", + "content-"+ locale +".json") + + // If dataset is empty, we try to load from short locale + // and if not present again, we switch to default one + var localeUnderscoreIndex = locale.indexOf('_') + if(!dataset) { + var localeShort; + // We will first look again for locale xx (without _XX if exist) + if(localeUnderscoreIndex > 0) { + localeShort = locale.substring(0, localeUnderscoreIndex) + } else { + localeShort = locale; + } + dataset = Lang.load(items.parser, langUrl, "words.json", + "content-"+localeShort+ ".json") + } + + // If still dataset is empty then fallback to english + if(!dataset) { + // English fallback + items.background.englishFallback = true + dataset = Lang.load(items.parser, langUrl, "words.json", "content-en.json") + } else { + items.background.englishFallback = false + } + + lessons = Lang.getAllLessons(dataset) + questions = initDataset() numberOfLevel = questions.length initLevel() } -function stop() -{ -} +function initDataset() { + var questions = [] + for (var lessonIndex = 0; lessonIndex < lessons.length; lessonIndex++) { + var lesson = Lang.getLessonWords(dataset, lessons[lessonIndex]) + var guessLetters = getRandomLetters(lesson) + questions[lessonIndex] = [] + for (var j in lesson) { + var clearQuestion = lesson[j].translatedTxt + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + clearQuestion = clearQuestion.toLocaleUpperCase() + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + clearQuestion = clearQuestion.toLocaleLowerCase() -function initLevel() -{ - items.bar.level = currentLevel + 1 - dataset = Core.shuffle(questions[currentLevel]) - currentQuestionNumber = 0 - nextQuestion() - items.currentQuestionNumberText.text = "1/" + dataset.length -} + var maskedQuestion = getRandomMaskedQuestion(clearQuestion, guessLetters, lessonIndex) -function nextLevel() -{ - if(numberOfLevel <= ++currentLevel ) - { - currentLevel = 0 + questions[lessonIndex].push( + { + 'image': lesson[j].image, + 'clearQuestion': clearQuestion, + 'maskedQuestion': maskedQuestion[0], + 'answer': maskedQuestion[1], + 'choices': maskedQuestion[2], + 'voice': lesson[j].voice, + }) + } } - initLevel(); + return questions } -function previousLevel() -{ - if(--currentLevel < 0) - { - currentLevel = numberOfLevel - 1 +// Get all the letters for all the words in the lesson excluding ignoreLetters +function getRandomLetters(lesson) { + var letters = [] + var re = new RegExp(ignoreLetters, 'g'); + for (var i in lesson) { + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').toLocaleUpperCase().split('')) + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').toLocaleLowerCase().split('')) + else + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').split('')) } - initLevel(); + return sortUnique(letters) } -function correctOptionPressed() -{ - nextQuestion() +// Get a random letter in the given word excluding ignoreLetters +function getRandomLetter(word) { + var re = new RegExp(ignoreLetters, 'g') + var letters = word.replace(re, '').split('') + var letter = Core.shuffle(letters)[0] + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + return letter.toLocaleUpperCase() + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + return letter.toLocaleLowerCase() + + return letter +} - if( currentQuestionNumber > dataset.length ) - currentQuestionNumber = 0 +function getRandomMaskedQuestion(clearQuestion, guessLetters, level) { + var maskedQuestion = clearQuestion + var goodLetter = getRandomLetter(maskedQuestion) + var index = maskedQuestion.search(goodLetter) - items.currentQuestionNumberText.text = - currentQuestionNumber + "/" + dataset.length + // Replace the char at index with '_' + var repl = maskedQuestion.split('') + repl[index] = '_' + maskedQuestion = repl.join('') + + // Get some other letter to confuse the children + var confusingLetters = [] + for(var i = 0; i < Math.min(level + 2, 6); i++) { + var letter = guessLetters.shift() + confusingLetters.push(letter) + guessLetters.push(letter) + } + confusingLetters.push(goodLetter) + + return [maskedQuestion, goodLetter, Core.shuffle(sortUnique(confusingLetters))] } -function wrongOptionPressed() +function sortUnique(arr) { + arr = arr.sort(function (a, b) { return a.localeCompare(b); }); + var ret = [arr[0]]; + for (var i = 1; i < arr.length; i++) { // start loop at 1 as element 0 can never be a duplicate + if (arr[i-1] !== arr[i]) { + ret.push(arr[i]); + } + } + return ret; +} +function stop() { - items.bar.opacity = 1 } -function getCorrectAnswer() +function initLevel() { - var question = dataset[currentQuestionNumber] - var currentQuestion = question.questionString - var i = 0 - for( ; i < currentQuestion.length ; ++i ) - { - if( "_" == currentQuestion.charAt(i) ) - { - break; - } - } + items.bar.level = currentLevel + 1 + items.score.currentSubLevel = 1 + items.score.numberOfSubLevels = questions[currentLevel].length + showQuestion() +} - return question.answerString.charAt(i) +function getCurrentQuestion() { + return questions[currentLevel][items.score.currentSubLevel - 1] } -// Take appropriate action based on the character being pressed -function answerPressed(character) +function showQuestion() { - if( character === getCorrectAnswer() ) - { - items.bonus.good("flower") - return true - } - else - { - items.bonus.bad("flower") - } - return false + var question = getCurrentQuestion() + + playWord(question.voice) + items.answer = question.answer + items.answers.model = question.choices + items.questionText.text = question.maskedQuestion + items.questionImage.source = "qrc:/gcompris/data/" + question.image } -function showAnswer() +function nextLevel() { - var question = dataset[currentQuestionNumber] - items.questionText.text = question.answerString - items.questionText.state = "answer" + if(numberOfLevel <= ++currentLevel ) + { + currentLevel = 0 + } + initLevel(); } -// Reset the screen values for next question -function nextQuestion() -{ - items.questionText.state = "question" - items.answers.model = [] +function nextSubLevel() { + var question = getCurrentQuestion() - if(++currentQuestionNumber >= dataset.length) { + if(++items.score.currentSubLevel > questions[currentLevel].length) { + items.bonus.good('flower') nextLevel() return } - - var question = dataset[currentQuestionNumber] - - var choice = question.choiceString - - var answersModel = new Array() - for(var i = 0 ; i < choice.length ; ++i) - answersModel.push(choice.charAt(i)) - items.answers.model = answersModel - - items.questionText.text = question.questionString - items.questionImage.source = question.pictureSource + showQuestion() } -// Add a new level which contains all the questions given in the -// questions list, combined together. -function createLastLevel() +function previousLevel() { - var lastData = [] - for(var level = 0 ; level < questions.length ; ++level) - for(var i = 0 ; i < questions.length ; ++i) - lastData.push(questions[level][i]) - - questions.push(lastData) + if(--currentLevel < 0) + { + currentLevel = numberOfLevel - 1 + } + initLevel(); +} +function showAnswer() +{ + var question = getCurrentQuestion() + playLetter(question.answer) + items.questionText.text = question.clearQuestion } function playLetter(letter) { - // WARNING This activity is english only for now - // replace en by $LOCALE once i18n support - items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/en/alphabet/" + items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/alphabet/" + Core.getSoundFilenamForChar(letter))) } + +function playCurrentWord() { + var question = getCurrentQuestion() + playWord(question.voice) +} + +function playWord(word) { + items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath(word)) +} + +function focusTextInput() { + if (!GCompris.ApplicationInfo.isMobile && items && items.textinput) + items.textinput.forceActiveFocus(); +} diff --git a/src/activities/missing-letter/resource/apple.png b/src/activities/missing-letter/resource/apple.png deleted file mode 100644 index a28dda7af..000000000 Binary files a/src/activities/missing-letter/resource/apple.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/bag.png b/src/activities/missing-letter/resource/bag.png deleted file mode 100644 index b5a6ef71d..000000000 Binary files a/src/activities/missing-letter/resource/bag.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/ball.png b/src/activities/missing-letter/resource/ball.png deleted file mode 100644 index caf06c779..000000000 Binary files a/src/activities/missing-letter/resource/ball.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/banana.png b/src/activities/missing-letter/resource/banana.png deleted file mode 100644 index d5ec2cfb5..000000000 Binary files a/src/activities/missing-letter/resource/banana.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/bed.png b/src/activities/missing-letter/resource/bed.png deleted file mode 100644 index 8e14b1881..000000000 Binary files a/src/activities/missing-letter/resource/bed.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/bottle.png b/src/activities/missing-letter/resource/bottle.png deleted file mode 100644 index 1154ee7dc..000000000 Binary files a/src/activities/missing-letter/resource/bottle.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/cake.png b/src/activities/missing-letter/resource/cake.png deleted file mode 100644 index 59e48e1a5..000000000 Binary files a/src/activities/missing-letter/resource/cake.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/car.png b/src/activities/missing-letter/resource/car.png deleted file mode 100644 index a7aaac505..000000000 Binary files a/src/activities/missing-letter/resource/car.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/dog.png b/src/activities/missing-letter/resource/dog.png deleted file mode 100644 index be3f22bdb..000000000 Binary files a/src/activities/missing-letter/resource/dog.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/fish.png b/src/activities/missing-letter/resource/fish.png deleted file mode 100644 index 5e5853167..000000000 Binary files a/src/activities/missing-letter/resource/fish.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/house.png b/src/activities/missing-letter/resource/house.png deleted file mode 100644 index fa9605211..000000000 Binary files a/src/activities/missing-letter/resource/house.png and /dev/null differ diff --git a/src/activities/missing-letter/resource/plane.png b/src/activities/missing-letter/resource/plane.png deleted file mode 100644 index 6ec0bec74..000000000 Binary files a/src/activities/missing-letter/resource/plane.png and /dev/null differ diff --git a/src/core/ApplicationInfo.cpp b/src/core/ApplicationInfo.cpp index 76d57dca1..321e9ef48 100644 --- a/src/core/ApplicationInfo.cpp +++ b/src/core/ApplicationInfo.cpp @@ -1,230 +1,235 @@ /* GCompris - ApplicationSettingsDefault.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "ApplicationInfo.h" #include #include #include #include #include #include #include #include #include #include #include QQuickWindow *ApplicationInfo::m_window = NULL; ApplicationInfo *ApplicationInfo::m_instance = NULL; ApplicationInfo::ApplicationInfo(QObject *parent): QObject(parent) { m_isMobile = false; #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(SAILFISHOS) m_isMobile = true; #endif #if defined(Q_OS_ANDROID) // Put android before checking linux/unix as it is also a linux m_platform = Android; #elif (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) m_platform = Linux; #elif defined(Q_OS_WIN) m_platform = Windows; #elif defined(Q_OS_MAC) m_platform = MacOSX; #elif defined(Q_OS_IOS) m_platform = Ios; #elif defined(Q_OS_BLACKBERRY) m_platform = Blackberry; #elif defined(SAILFISHOS) m_platform = SailfishOS; #endif QRect rect = qApp->primaryScreen()->geometry(); m_ratio = qMin(qMax(rect.width(), rect.height())/800. , qMin(rect.width(), rect.height())/520.); // calculate a factor for font-scaling, cf. // http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio qreal refDpi = 216.; qreal refHeight = 1776.; qreal refWidth = 1080.; qreal height = qMax(rect.width(), rect.height()); qreal width = qMin(rect.width(), rect.height()); qreal dpi = qApp->primaryScreen()->logicalDotsPerInch(); m_fontRatio = qMax(qreal(1.0), qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth))); m_isPortraitMode = m_isMobile ? rect.height() > rect.width() : false; m_applicationWidth = m_isMobile ? rect.width() : 1120; if (m_isMobile) connect(qApp->primaryScreen(), SIGNAL(physicalSizeChanged(QSizeF)), this, SLOT(notifyPortraitMode())); // Get all symbol fonts to remove them QFontDatabase database; m_excludedFonts = database.families(QFontDatabase::Symbol); // Get fonts from rcc const QStringList fontFilters = {"*.otf", "*.ttf"}; m_fontsFromRcc = QDir(":/gcompris/src/core/resource/fonts").entryList(fontFilters); } ApplicationInfo::~ApplicationInfo() { m_instance = NULL; } void ApplicationInfo::setApplicationWidth(const int newWidth) { if (newWidth != m_applicationWidth) { m_applicationWidth = newWidth; emit applicationWidthChanged(); } } QString ApplicationInfo::getResourceDataPath() { return QString("qrc:/gcompris/data"); } QString ApplicationInfo::getFilePath(const QString &file) { #if defined(Q_OS_ANDROID) return QString("assets:/%1").arg(file); #elif defined(Q_OS_MAC) return QString("%1/rcc/%2").arg(QCoreApplication::applicationDirPath(), file); #else return QString("%1/%2/rcc/%3").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER, file); #endif } QString ApplicationInfo::getAudioFilePath(const QString &file) { QString localeName = getVoicesLocale(ApplicationSettings::getInstance()->locale()); + return getAudioFilePathForLocale(file, localeName); +} +QString ApplicationInfo::getAudioFilePathForLocale(const QString &file, + const QString &localeName) +{ QString filename = file; filename.replace("$LOCALE", localeName); filename.replace("$CA", COMPRESSED_AUDIO); if(file.startsWith('/') || file.startsWith(QLatin1String("qrc:")) || file.startsWith(':')) return filename; else return getResourceDataPath() + '/' + filename; } QString ApplicationInfo::getLocaleFilePath(const QString &file) { QString localeShortName = localeShort(); QString filename = file; filename.replace("$LOCALE", localeShortName); return filename; } QStringList ApplicationInfo::getSystemExcludedFonts() { return m_excludedFonts; } QStringList ApplicationInfo::getFontsFromRcc() { return m_fontsFromRcc; } void ApplicationInfo::notifyPortraitMode() { int width = qApp->primaryScreen()->geometry().width(); int height = qApp->primaryScreen()->geometry().height(); setIsPortraitMode(height > width); } void ApplicationInfo::setIsPortraitMode(const bool newMode) { if (m_isPortraitMode != newMode) { m_isPortraitMode = newMode; emit portraitModeChanged(); } } void ApplicationInfo::setWindow(QQuickWindow *window) { m_window = window; } void ApplicationInfo::screenshot(QString const &path) { QImage img = m_window->grabWindow(); img.save(path); } void ApplicationInfo::notifyFullscreenChanged() { if(ApplicationSettings::getInstance()->isFullscreen()) m_window->showFullScreen(); else m_window->showNormal(); } // return the shortest possible locale name for the given locale, describing // a unique voices dataset QString ApplicationInfo::getVoicesLocale(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); if(_locale == "C") _locale = "en_US"; } // locales we have country-specific voices for: if (_locale.startsWith(QLatin1String("pt_BR")) || _locale.startsWith(QLatin1String("zh_CN")) || _locale.startsWith(QLatin1String("zh_TW"))) return QLocale(_locale).name(); // short locale for all the rest: return localeShort(_locale); } QObject *ApplicationInfo::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) /* * Connect the fullscreen change signal to applicationInfo in order to change * the QQuickWindow value */ ApplicationInfo* appInfo = getInstance(); connect(ApplicationSettings::getInstance(), SIGNAL(fullscreenChanged()), appInfo, SLOT(notifyFullscreenChanged())); return appInfo; } void ApplicationInfo::init() { qmlRegisterSingletonType("GCompris", 1, 0, "ApplicationInfo", systeminfoProvider); } diff --git a/src/core/ApplicationInfo.h b/src/core/ApplicationInfo.h index 2b1289512..b0409f929 100644 --- a/src/core/ApplicationInfo.h +++ b/src/core/ApplicationInfo.h @@ -1,320 +1,336 @@ /* GCompris - ApplicationSettingsDefault.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef APPLICATIONINFO_H #define APPLICATIONINFO_H #include #include "ApplicationSettings.h" #include #include #include #include class QQuickWindow; /** * @class ApplicationInfo * @short A general purpose singleton that exposes miscellaneous native * functions to the QML layer. * @ingroup infrastructure */ class ApplicationInfo : public QObject { Q_OBJECT Q_ENUMS(Platform) /** * Width of the application viewport. */ Q_PROPERTY(int applicationWidth READ applicationWidth WRITE setApplicationWidth NOTIFY applicationWidthChanged) /** * Platform the application is currently running on. */ Q_PROPERTY(Platform platform READ platform CONSTANT) /** * Whether the application is currently running on a mobile platform. * * Mobile platforms are Android, Ios (not supported yet), * Blackberry (not supported) */ Q_PROPERTY(bool isMobile READ isMobile CONSTANT) /** * Whether the current platform supports fragment shaders. * * This flag is used in some core modules to selectively deactivate * particle effects which cause crashes on some Android devices. * * cf. https://bugreports.qt.io/browse/QTBUG-44194 * * For now always set to false, to prevent crashes. */ Q_PROPERTY(bool hasShader READ hasShader CONSTANT) /** * Whether currently in portrait mode, on mobile platforms. * * Based on viewport geometry. */ Q_PROPERTY(bool isPortraitMode READ isPortraitMode WRITE setIsPortraitMode NOTIFY portraitModeChanged) /** * Ratio factor used for scaling of sizes on high-dpi devices. * * Must be used by activities as a scaling factor to all pixel values. */ Q_PROPERTY(qreal ratio READ ratio NOTIFY ratioChanged) /** * Ratio factor used for font scaling. * * On some low-dpi Android devices with high res (e.g. Galaxy Tab 4) the * fonts in Text-like elements appear too small with respect to the other * graphics -- also if we are using font.pointSize. * * For these cases we calculate a fontRatio in ApplicationInfo that takes * dpi information into account, as proposed on * http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio * * GCText applies this factor automatically on its new fontSize property. */ Q_PROPERTY(qreal fontRatio READ fontRatio NOTIFY fontRatioChanged) /** * Short (2-letter) locale string of the currently active language. */ Q_PROPERTY(QString localeShort READ localeShort) /** * GCompris version string (compile time). */ Q_PROPERTY(QString GCVersion READ GCVersion CONSTANT) /** * Qt version string (runtime). */ Q_PROPERTY(QString QTVersion READ QTVersion CONSTANT) /** * Audio codec used for voices resources. * * This is determined at compile time (ogg for free platforms, aac on * MacOSX and IOS). */ Q_PROPERTY(QString CompressedAudio READ CompressedAudio CONSTANT) /** * Download allowed * * This is determined at compile time. If set to NO GCompris will * never download anything. */ Q_PROPERTY(bool isDownloadAllowed READ isDownloadAllowed CONSTANT) public: /** * Known host platforms. */ enum Platform { Linux, /**< Linux (except Android) */ Windows, /**< Windows */ MacOSX, /**< MacOSX */ Android, /**< Android */ Ios, /**< IOS (not supported) */ Blackberry, /**< Blackberry (not supported) */ SailfishOS /**< SailfishOS */ }; /** * Registers singleton in the QML engine. * * It is not recommended to create a singleton of Qml Singleton registered * object but we could not found a better way to let us access ApplicationInfo * on the C++ side. All our test shows that it works. */ static void init(); /** * Returns an absolute and platform independent path to the passed @p file * * @param file A relative filename. * @returns Absolute path to the file. */ static QString getFilePath(const QString &file); /** * Returns the short locale name for the passed @p locale. * * Handles also 'system' (GC_DEFAULT_LOCALE) correctly which resolves to * QLocale::system().name(). * * @param locale A locale string of the form \_\ * @returns A short locale string of the form \ */ static QString localeShort(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); } // Can't use left(2) because of Asturian where there are 3 chars return _locale.left(_locale.indexOf('_')); } /** * Returns a locale string that can be used in voices filenames. * * @param locale A locale string of the form \_\ * @returns A locale string as used in voices filenames. */ Q_INVOKABLE QString getVoicesLocale(const QString &locale); /** * Request GCompris to take the Audio Focus at the system level. * * On systems that support it, it will mute a running audio player. */ Q_INVOKABLE bool requestAudioFocus() const; /** * Abandon the Audio Focus. * * On systems that support it, it will let an audio player start again. */ Q_INVOKABLE void abandonAudioFocus() const; /// @cond INTERNAL_DOCS static ApplicationInfo *getInstance() { if(!m_instance) { m_instance = new ApplicationInfo(); } return m_instance; } static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static void setWindow(QQuickWindow *window); explicit ApplicationInfo(QObject *parent = 0); ~ApplicationInfo(); int applicationWidth() const { return m_applicationWidth; } void setApplicationWidth(const int newWidth); Platform platform() const { return m_platform; } bool isPortraitMode() const { return m_isPortraitMode; } void setIsPortraitMode(const bool newMode); bool isMobile() const { return m_isMobile; } bool hasShader() const { return false; } qreal ratio() const { return m_ratio; } qreal fontRatio() const { return m_fontRatio; } QString localeShort() const { return localeShort( ApplicationSettings::getInstance()->locale() ); } static QString GCVersion() { return VERSION; } static QString QTVersion() { return qVersion(); } static QString CompressedAudio() { return COMPRESSED_AUDIO; } static bool isDownloadAllowed() { return DOWNLOAD_ALLOWED == "ON"; } /// @endcond protected slots: /** * Returns the resource root-path used for GCompris resources. */ QString getResourceDataPath(); - /** + /** * Returns an absolute path to a language specific sound/voices file. If * the file is already absolute only the token replacement is applied. - * - * @param file A templated relative path to a language specific file. Any - * occurrence of the '$LOCALE' placeholder will be replaced by - * the currently active locale string. + * + * @param file A templated relative path to a language specific file. Any + * occurrence of the '$LOCALE' placeholder will be replaced by + * the currently active locale string. * Any occurrence of '$CA' placeholder will be replaced by * the current compressed audio format ('ogg' or 'aac). * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' - * @returns An absolute path to the corresponding resource file. - */ + * @returns An absolute path to the corresponding resource file. + */ Q_INVOKABLE QString getAudioFilePath(const QString &file); + /** + * Returns an absolute path to a language specific sound/voices file. If + * the file is already absolute only the token replacement is applied. + * + * @param file A templated relative path to a language specific file. Any + * occurrence of the '$LOCALE' placeholder will be replaced by + * the currently active locale string. + * Any occurrence of '$CA' placeholder will be replaced by + * the current compressed audio format ('ogg' or 'aac). + * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' + * @param locale the locale for which to get this audio file + * @returns An absolute path to the corresponding resource file. + */ + Q_INVOKABLE QString getAudioFilePathForLocale(const QString &file, + const QString &localeName); + /** * Returns an absolute path to a language specific resource file. * * Generalization of getAudioFilePath(). * @sa getAudioFilePath */ Q_INVOKABLE QString getLocaleFilePath(const QString &file); /** * @returns A list of systems-fonts that should be excluded from font * selection. */ Q_INVOKABLE QStringList getSystemExcludedFonts(); /** * @returns A list of fonts contained in the fonts resources. */ Q_INVOKABLE QStringList getFontsFromRcc(); /** * Stores a screenshot in the passed @p file. * * @param file Absolute destination filename. */ Q_INVOKABLE void screenshot(const QString &file); void notifyPortraitMode(); Q_INVOKABLE void notifyFullscreenChanged(); protected: qreal getSizeWithRatio(const qreal height) { return ratio() * height; } signals: void applicationWidthChanged(); void portraitModeChanged(); void ratioChanged(); void fontRatioChanged(); void applicationSettingsChanged(); void fullscreenChanged(); private: static ApplicationInfo *m_instance; int m_applicationWidth; Platform m_platform; QQmlPropertyMap *m_constants; bool m_isPortraitMode; bool m_isMobile; qreal m_ratio; qreal m_fontRatio; // Symbols fonts that user can't see QStringList m_excludedFonts; QStringList m_fontsFromRcc; static QQuickWindow *m_window; }; #endif // APPLICATIONINFO_H diff --git a/src/core/DialogActivityConfig.qml b/src/core/DialogActivityConfig.qml index 5b546a019..2c2ef815e 100644 --- a/src/core/DialogActivityConfig.qml +++ b/src/core/DialogActivityConfig.qml @@ -1,218 +1,224 @@ /* GCompris - DialogActivityConfig.qml * * Copyright (C) 2014 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.2 import GCompris 1.0 /** * A QML component for a full screen configuration dialog. * @ingroup components * * All user editable settings are presented to the user in a * DialogActivityConfig dialog. The global configuration can be accessed * through the Bar in the main menu, activity specific configuration from the * respective activity. * * All config items that are shown in this dialog are persisted * using ApplicationSettings. * * For an example have a look at Menu.qml. * * For more details on how to add configuration to an activity cf. * [the wiki](http://gcompris.net/wiki/Qt_Quick_development_process#Adding_a_configuration_for_a_specific_activity) * * @sa ApplicationSettings * @inherit QtQuick.Item */ Rectangle { id: dialogActivityContent + visible: false /* Public interface: */ /** * type:object * The content object as loaded dynamically. */ property alias configItem: loader.item /** * type:Component * Content component which holds the visual presentation of the * config settings in the QML scene. */ property Component content /** * type:string * The name of the activity in case of per-activity config. * * Will be autogenerated unless set by the caller. */ property string activityName: "" /** * type:object * Map containing all settings as key/value-pairs. * * Will be populated from ApplicationSettings.loadActivityConfiguration * and can be passed to ApplicationSettings.saveActivityConfiguration. */ property var dataToSave /// @cond INTERNAL_DOCS property bool isDialog: true /** * type:string * Title of the configuration dialog. * Global configuration name is "Configuration". * For activities, it is "activity name configuration". */ readonly property string title: { if(activityName != "") qsTr("%1 configuration").arg(activityInfo.title) else qsTr("Configuration") } property alias active: loader.active property alias loader: loader property QtObject activityInfo: ActivityInfoTree.currentActivity property ActivityBase currentActivity /// @endcond /** * Emitted when the config dialog has been closed. */ signal close /** * Emitted when the config dialog has been started. */ signal start /** * Emitted when the settings are to be saved. * * The actual persisting of the settings in the settings file is done by * DialogActivityConfig. The activity has to take care to update its * internal state. */ signal saveData /** * Emitted when the config settings have been loaded. */ signal loadData signal stop color: "#696da3" border.color: "black" border.width: 1 z: 1000 function getInitialConfiguration() { if(activityName == "") { activityName = ActivityInfoTree.currentActivity.name.split('/')[0]; } dataToSave = ApplicationSettings.loadActivityConfiguration(activityName) loadData() } + function saveDatainConfiguration() { + saveData() + ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) + } + Row { visible: dialogActivityContent.active spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogActivityContent.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 Image { id: titleIcon anchors { left: parent.left top: parent.top margins: 4 * ApplicationInfo.ratio } } GCText { id: title text: dialogActivityContent.title width: dialogActivityContent.width - 30 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogActivityContent.width - 30 height: dialogActivityContent.height - 100 border.color: "black" border.width: 2 anchors.margins: 100 Flickable { id: flick anchors.margins: 8 anchors.fill: parent flickableDirection: Flickable.VerticalFlick clip: true contentHeight: contentItem.childrenRect.height + 40 * ApplicationInfo.ratio Loader { id: loader active: false sourceComponent: dialogActivityContent.content property alias rootItem: dialogActivityContent } } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { onClose: { saveData() ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) parent.close() } } } diff --git a/src/core/DownloadManager.cpp b/src/core/DownloadManager.cpp index f1830d9e7..07779bfcc 100644 --- a/src/core/DownloadManager.cpp +++ b/src/core/DownloadManager.cpp @@ -1,645 +1,657 @@ /* GCompris - DownloadManager.cpp * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "DownloadManager.h" #include "ApplicationSettings.h" #include "ApplicationInfo.h" #include #include #include #include #include #include #include const QString DownloadManager::contentsFilename = QString("Contents"); DownloadManager* DownloadManager::_instance = 0; /* Public interface: */ DownloadManager::DownloadManager() : accessManager(this), serverUrl(ApplicationSettings::getInstance()->downloadServerUrl()) { // Cleanup of previous data directory no more used QDir previousDataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/data"; if(previousDataLocation.exists()) { qDebug() << "Remove previous directory data: " << previousDataLocation; previousDataLocation.removeRecursively(); } } DownloadManager::~DownloadManager() { shutdown(); _instance = 0; } void DownloadManager::shutdown() { qDebug() << "DownloadManager: shutting down," << activeJobs.size() << "active jobs"; abortDownloads(); } // It is not recommended to create a singleton of Qml Singleton registered // object but we could not found a better way to let us access DownloadManager // on the C++ side. All our test shows that it works. // Using the singleton after the QmlEngine has been destroyed is forbidden! DownloadManager* DownloadManager::getInstance() { if (!_instance) _instance = new DownloadManager; return _instance; } QObject *DownloadManager::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void DownloadManager::init() { qmlRegisterSingletonType("GCompris", 1, 0, "DownloadManager", systeminfoProvider); } bool DownloadManager::downloadIsRunning() const { return (activeJobs.size() > 0); } void DownloadManager::abortDownloads() { if (activeJobs.size() > 0) { QMutexLocker locker(&jobsMutex); QMutableListIterator iter(activeJobs); while (iter.hasNext()) { DownloadJob *job = iter.next(); if (job->reply) { disconnect(job->reply, SIGNAL(finished()), this, SLOT(downloadFinished())); disconnect(job->reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); if (job->reply->isRunning()) { qDebug() << "Aborting download job:" << job->url; job->reply->abort(); job->file.close(); job->file.remove(); } delete job->reply; } iter.remove(); } locker.unlock(); emit error(QNetworkReply::OperationCanceledError, "Download cancelled by user"); } } QString DownloadManager::getVoicesResourceForLocale(const QString& locale) const { return QString("data2/voices-" COMPRESSED_AUDIO "/voices-%1.rcc") .arg(ApplicationInfo::getInstance()->getVoicesLocale(locale)); } inline QString DownloadManager::getAbsoluteResourcePath(const QString& path) const { foreach (const QString &base, getSystemResourcePaths()) { if (QFile::exists(base + '/' + path)) return QString(base + '/' + path); } return QString(); } // @FIXME should support a variable subpath length like data2/full.rcc" inline QString DownloadManager::getRelativeResourcePath(const QString& path) const { QStringList parts = path.split('/', QString::SkipEmptyParts); if (parts.size() < 3) return QString(); return QString(parts[parts.size()-3] + '/' + parts[parts.size()-2] + '/' + parts[parts.size()-1]); } bool DownloadManager::haveLocalResource(const QString& path) const { return (!getAbsoluteResourcePath(path).isEmpty()); } bool DownloadManager::updateResource(const QString& path) { if (checkDownloadRestriction()) return downloadResource(path); // check for updates and register else { QString absPath = getAbsoluteResourcePath(path); // automatic download prohibited -> register if available if (!absPath.isEmpty()) - return registerResource(absPath); + return registerResourceAbsolute(absPath); else { qDebug() << "No such local resource and download prohibited:" << path; return false; } } } bool DownloadManager::downloadResource(const QString& path) { qDebug() << "Downloading resource file" << path; DownloadJob* job = new DownloadJob(QUrl(serverUrl.toString() + '/' + path)); { QMutexLocker locker(&jobsMutex); activeJobs.append(job); } if (!download(job)) { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); return false; } return true; } #if 0 // vvv might be helpful later with other use-cases: void DownloadManager::registerLocalResources() { QStringList filenames = getLocalResources(); if (filenames == QStringList()) { qDebug() << "No local resources found"; return; } QList::const_iterator iter; for (iter = filenames.constBegin(); iter != filenames.constEnd(); iter++) registerResource(*iter); } bool DownloadManager::checkForUpdates() { QStringList filenames = getLocalResources(); if (filenames == QStringList()) { qDebug() << "No local resources found"; return true; } if (!checkDownloadRestriction()) { qDebug() << "Can't download with current network connection (" << networkConfiguration.bearerTypeName() << ")!"; return false; } QList::const_iterator iter; DownloadJob *job = new DownloadJob(); for (iter = filenames.constBegin(); iter != filenames.constEnd(); iter++) { QUrl url = getUrlForFilename(*iter); qDebug() << "Scheduling resource for update: " << url; job->queue.append(url); } job->url = job->queue.takeFirst(); { QMutexLocker locker(&jobsMutex); activeJobs.append(job); } if (!download(job)) { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); return false; } return true; } #endif /* Private: */ inline QString DownloadManager::tempFilenameForFilename(const QString &filename) const { return QString(filename).append("_"); } inline QString DownloadManager::filenameForTempFilename(const QString &tempFilename) const { if (tempFilename.endsWith(QLatin1String("_"))) return tempFilename.left(tempFilename.length() - 1); return tempFilename; } bool DownloadManager::download(DownloadJob* job) { QNetworkRequest request; // First download Contents file for verification if not yet done: if (!job->contents.contains(job->url.fileName())) { int len = job->url.fileName().length(); QUrl contentsUrl = QUrl(job->url.toString().remove(job->url.toString().length() - len, len) + contentsFilename); if (!job->knownContentsUrls.contains(contentsUrl)) { // Note: need to track already tried Contents files or we can end // up in an infinite loop if corresponding Contents file does not // exist upstream job->knownContentsUrls.append(contentsUrl); //qDebug() << "Postponing rcc download, first fetching Contents" << contentsUrl; job->queue.prepend(job->url); job->url = contentsUrl; } } QFileInfo fi(getFilenameForUrl(job->url)); // make sure target path exists: QDir dir; if (!dir.exists(fi.path()) && !dir.mkpath(fi.path())) { qDebug() << "Could not create resource path " << fi.path(); emit error(QNetworkReply::ProtocolUnknownError, "Could not create resource path"); return false; } job->file.setFileName(tempFilenameForFilename(fi.filePath())); if (!job->file.open(QIODevice::WriteOnly)) { emit error(QNetworkReply::ProtocolUnknownError, QString("Could not open target file %1").arg(job->file.fileName())); return false; } // start download: request.setUrl(job->url); //qDebug() << "Now downloading" << job->url << "to" << fi.filePath() << "..."; QNetworkReply *reply = accessManager.get(request); job->reply = reply; connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); connect(reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); if (job->url.fileName() != contentsFilename) { connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64))); emit downloadStarted(job->url.toString().remove(0, serverUrl.toString().length())); } return true; } inline DownloadManager::DownloadJob* DownloadManager::getJobByReply(QNetworkReply *r) { QMutexLocker locker(&jobsMutex); for (int i = 0; i < activeJobs.size(); i++) if (activeJobs[i]->reply == r) return activeJobs[i]; return NULL; // should never happen! } void DownloadManager::downloadReadyRead() { QNetworkReply *reply = dynamic_cast(sender()); DownloadJob *job = getJobByReply(reply); job->file.write(reply->readAll()); } inline QString DownloadManager::getFilenameForUrl(const QUrl& url) const { QString relPart = url.toString().remove(0, serverUrl.toString().length()); return QString(getSystemDownloadPath() + relPart); } inline QUrl DownloadManager::getUrlForFilename(const QString& filename) const { return QUrl(serverUrl.toString() + '/' + getRelativeResourcePath(filename)); } inline QString DownloadManager::getSystemDownloadPath() const { return QStandardPaths::writableLocation(QStandardPaths::DataLocation); } inline QStringList DownloadManager::getSystemResourcePaths() const { QStringList results({ getSystemDownloadPath(), #if defined(Q_OS_ANDROID) "assets:", #endif QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + '/' + GCOMPRIS_APPLICATION_NAME }); // Append standard application directories (like /usr/share/applications/) foreach(const QString &path, QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { results.append(path + '/' + GCOMPRIS_APPLICATION_NAME); } return results; } bool DownloadManager::checkDownloadRestriction() const { #if 0 // note: Something like the following can be used once bearer mgmt // has been implemented for android (cf. Qt bug #30394) QNetworkConfiguration::BearerType conn = networkConfiguration.bearerType(); qDebug() << "Bearer type: "<< conn << ": "<< networkConfiguration.bearerTypeName(); if (!ApplicationSettings::getInstance()->isMobileNetworkDownloadsEnabled() && conn != QNetworkConfiguration::BearerEthernet && conn != QNetworkConfiguration::QNetworkConfiguration::BearerWLAN) return false; return true; #endif return ApplicationSettings::getInstance()->isAutomaticDownloadsEnabled() && ApplicationInfo::getInstance()->isDownloadAllowed(); } void DownloadManager::handleError(QNetworkReply::NetworkError code) { Q_UNUSED(code); QNetworkReply *reply = dynamic_cast(sender()); emit error(reply->error(), reply->errorString()); } bool DownloadManager::parseContents(DownloadJob *job) { if (job->file.isOpen()) job->file.close(); if (!job->file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Could not open file " << job->file.fileName(); return false; } /* * We expect the line-syntax, that md5sum and colleagues creates: * * 53f0a3eb206b3028500ca039615c5f84 voices-en.rcc */ QTextStream in(&job->file); while (!in.atEnd()) { QString line = in.readLine(); QStringList parts = line.split(' ', QString::SkipEmptyParts); if (parts.size() != 2) { qWarning() << "Invalid format of Contents file!"; return false; } job->contents[parts[1]] = parts[0]; //qDebug() << "Contents: " << parts[1] << " -> " << parts[0]; } job->file.close(); return true; } bool DownloadManager::checksumMatches(DownloadJob *job, const QString& filename) const { Q_ASSERT(job->contents != (QMap())); if (!QFile::exists(filename)) return false; QString basename = QFileInfo(filename).fileName(); if (!job->contents.contains(basename)) return false; QFile file(filename); file.open(QIODevice::ReadOnly); QCryptographicHash fileHasher(hashMethod); if (!fileHasher.addData(&file)) { qWarning() << "Could not read file for hashing: " << filename; return false; } file.close(); QByteArray fileHash = fileHasher.result().toHex(); //qDebug() << "Checking file-hash ~ contents-hash: " << fileHash << " ~ " << job->contents[basename]; return (fileHash == job->contents[basename]); } void DownloadManager::unregisterResource_locked(const QString& filename) { if (!QResource::unregisterResource(filename)) qDebug() << "Error unregistering resource file" << filename; else { qDebug() << "Successfully unregistered resource file" << filename; registeredResources.removeOne(filename); } } inline bool DownloadManager::isRegistered(const QString& filename) const { return (registeredResources.indexOf(filename) != -1); } -bool DownloadManager::registerResource(const QString& filename) +/* + * Registers an rcc file given by absolute path + */ +bool DownloadManager::registerResourceAbsolute(const QString& filename) { QMutexLocker locker(&rcMutex); if (isRegistered(filename)) unregisterResource_locked(filename); if (!QResource::registerResource(filename)) { qDebug() << "Error registering resource file" << filename; return false; } else { qDebug() << "Successfully registered resource" << filename; registeredResources.append(filename); locker.unlock(); /* note: we unlock before emitting to prevent * potential deadlocks */ emit resourceRegistered(getRelativeResourcePath(filename)); QString v = getVoicesResourceForLocale( ApplicationSettings::getInstance()->locale()); if (v == getRelativeResourcePath(filename)) emit voicesRegistered(); - return false; + return true; } } +/* + * Registers an rcc file given by a relative resource path + */ +bool DownloadManager::registerResource(const QString& filename) +{ + return registerResourceAbsolute(getAbsoluteResourcePath(filename)); +} + bool DownloadManager::isDataRegistered(const QString& data) const { QString res = QString(":/gcompris/data/%1").arg(data); return !QDir(res).entryList().empty(); } bool DownloadManager::areVoicesRegistered() const { QString resource = QString("voices-" COMPRESSED_AUDIO "/%1"). arg(ApplicationInfo::getInstance()->getVoicesLocale(ApplicationSettings::getInstance()->locale())); return isDataRegistered(resource); } void DownloadManager::downloadFinished() { QNetworkReply* reply = dynamic_cast(sender()); DownloadFinishedCode code = Success; DownloadJob *job = getJobByReply(reply); if (job->file.isOpen()) { job->file.flush(); // note: important, or checksums might be wrong! job->file.close(); } if (reply->error() && job->file.exists()) job->file.remove(); else { // active temp file QString tFilename = filenameForTempFilename(job->file.fileName()); if (QFile::exists(tFilename)) QFile::remove(tFilename); if (!job->file.rename(tFilename)) qWarning() << "Could not rename temporary file to" << tFilename; } QString targetFilename = getFilenameForUrl(job->url); if (job->url.fileName() == contentsFilename) { // Contents if (reply->error()) { qWarning() << "Error downloading Contents from" << job->url << ":" << reply->error() << ":" << reply->errorString(); // note: errorHandler() emit's error! code = Error; goto outError; } //qDebug() << "Download of Contents finished successfully: " << job->url; if (!parseContents(job)) { qWarning() << "Invalid format of Contents file" << job->url; emit error(QNetworkReply::UnknownContentError, "Invalid format of Contents file"); code = Error; goto outError; } } else { // RCC file if (reply->error()) { qWarning() << "Error downloading RCC file from " << job->url << ":" << reply->error() << ":" << reply->errorString(); // note: errorHandler() emit's error! code = Error; - // register already existing files: - if (QFile::exists(targetFilename)) - registerResource(targetFilename); + // register already existing files (if not yet done): + if (QFile::exists(targetFilename) && !isRegistered(targetFilename)) + registerResourceAbsolute(targetFilename); } else { qDebug() << "Download of RCC file finished successfully: " << job->url; if (!checksumMatches(job, targetFilename)) { qWarning() << "Checksum of downloaded file does not match: " << targetFilename; emit error(QNetworkReply::UnknownContentError, QString("Checksum of downloaded file does not match: %1") .arg(targetFilename)); code = Error; } else - registerResource(targetFilename); + registerResourceAbsolute(targetFilename); } } // try next: while (!job->queue.isEmpty()) { job->url = job->queue.takeFirst(); QString relPath = getRelativeResourcePath(getFilenameForUrl(job->url)); // check in each resource-path for an up2date rcc file: foreach (const QString &base, getSystemResourcePaths()) { QString filename = base + '/' + relPath; if (QFile::exists(filename) && checksumMatches(job, filename)) { // file is up2date, register! necessary: qDebug() << "Local resource is up-to-date:" << QFileInfo(filename).fileName(); - registerResource(filename); + if (!isRegistered(filename)) // no update and already registered -> noop + registerResourceAbsolute(filename); code = NoChange; break; } } if (code != NoChange) // nothing is up2date locally -> download it if (download(job)) goto outNext; } // none left, DownloadJob finished if (job->file.isOpen()) job->file.close(); { // note: must remove before signalling downloadFinished(), otherwise race condition for the Qt.quit() case QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); } emit downloadFinished(code); delete reply; delete job; return; outError: if (job->url.fileName() == contentsFilename) { // if we could not download the contents file register local existing // files for outstanding jobs: QUrl nUrl; while (!job->queue.isEmpty()) { nUrl = job->queue.takeFirst(); QString relPath = getRelativeResourcePath(getFilenameForUrl(nUrl)); foreach (const QString &base, getSystemResourcePaths()) { QString filename = base + '/' + relPath; if (QFile::exists(filename)) - registerResource(filename); + registerResourceAbsolute(filename); } } } if (job->file.isOpen()) job->file.close(); { QMutexLocker locker(&jobsMutex); activeJobs.removeOne(job); } delete reply; delete job; return; outNext: // next sub-job started delete reply; return; } QStringList DownloadManager::getLocalResources() { QStringList result; foreach (const QString &path, getSystemResourcePaths()) { QDir dir(path); if (!dir.exists(path) && !dir.mkpath(path)) { qWarning() << "Could not create resource path " << path; continue; } QDirIterator it(dir, QDirIterator::Subdirectories); while (it.hasNext()) { QString filename = it.next(); QFileInfo fi = it.fileInfo(); if (fi.isFile() && (filename.endsWith(QLatin1String(".rcc")))) result.append(filename); } } return result; } diff --git a/src/core/DownloadManager.h b/src/core/DownloadManager.h index 067a8c184..347cdcec6 100644 --- a/src/core/DownloadManager.h +++ b/src/core/DownloadManager.h @@ -1,371 +1,378 @@ /* GCompris - DownloadManager.h * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H #include #include #include #include #include #include #include #include #include #include #include /** * @class DownloadManager * @short A singleton class responsible for downloading, updating and * maintaining remote resources. * @ingroup infrastructure * * DownloadManager is responsible for downloading, updating and registering * additional resources (in Qt's binary .rcc format) used by GCompris. * It downloads from a upstream URL (ApplicationSettings.downloadServerUrl) to the * default local writable location. Downloads are based on common relative * resource paths, such that a URL of the form * * http://\/\ * * will be downloaded to a local path * * /\/\ * * and registered with a resource root path * * qrc:/\/ * * Internally resources are uniquely identified by their relative resource * path * * \ * (e.g. data2/voices-ogg/voices-en.rcc>) * * Downloading and verification of local files is controlled by MD5 * checksums that are expected to be stored in @c Contents files in each * upstream directory according to the syntax produced by the @c md5sum * tool. The checksums are used for checking whether a local rcc file is * up-to-date (to avoid unnecesary rcc downloads) and to verify that the * transfer was complete. Only valid rcc files (with correct checksums) * are registered. * * A resource file must reference the "/gcompris/data" prefix with * \. All data are loaded and referenced * from this prefix. It is possible to check if a data is registered with * isDataRegistered. * * @sa DownloadDialog, ApplicationSettings.downloadServerUrl, * ApplicationSettings.isAutomaticDownloadsEnabled */ class DownloadManager : public QObject { Q_OBJECT private: DownloadManager(); // prohibit external creation, we are a singleton! static DownloadManager* _instance; // singleton instance /** Container for a full download job */ typedef struct DownloadJob { QUrl url; ///< url of the currently running sub-job QFile file; ///< target file for the currently running sub-job QNetworkReply *reply; ///< reply object for the currently running sub-job QList queue; ///< q of remaining sub jobs (QList for convenience) QMap contents; ///< checksum map for download verification QList knownContentsUrls; ///< store already tried upstream Contents files (for infinite loop protection) DownloadJob(QUrl u = QUrl()) : url(u), file(), reply(0), queue(QList()) {} } DownloadJob; QList activeJobs; ///< track active jobs to allow for parallel downloads QMutex jobsMutex; ///< not sure if we need to expect concurrent access, better lockit! static const QString contentsFilename; static const QCryptographicHash::Algorithm hashMethod = QCryptographicHash::Md5; QList registeredResources; QMutex rcMutex; ///< not sure if we need to expect concurrent access, better lockit! QNetworkAccessManager accessManager; QUrl serverUrl; /** * Get the platform-specific path storing downloaded resources. * * Uses QStandardPaths::writableLocation(QStandardPaths::DataLocation) * which returns * - on desktop $HOME/.local/share/KDE/gcompris-qt/ * - on android /data/data/net.gcompris/files * * @return An absolute path. */ QString getSystemDownloadPath() const; /** * Get all paths that are used for storing resources. * * @returns A list of absolute paths used for storing local resources. * The caller should keep the returned list order when looking for * resources, for now the lists contains: * 1. getSystemDownloadPath() * 2. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)/gcompris-qt * which is * - $HOME/.local/share/gcompris-qt (on linux desktop) * - /storage/sdcard0/GCompris (on android) * 3. [QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)]/gcompris-qt * which is on GNU/Linux * - $HOME/.local/share/KDE/gcompris-qt * - $HOME/.local/share/gcompris-qt * - $HOME/.local/share/applications/gcompris-qt * - /usr/share/applications/gcompris-qt * - /usr/local/share/applications/gcompris-qt */ QStringList getSystemResourcePaths() const; QString getResourceRootForFilename(const QString& filename) const; QString getFilenameForUrl(const QUrl& url) const; QUrl getUrlForFilename(const QString& filename) const; /** * Transforms the passed relative path to an absolute resource path of an * existing .rcc file, honouring the order of the systemResourcePaths * * @returns The absolute path of the .rcc file if it exists, QString() * otherwise */ QString getAbsoluteResourcePath(const QString& path) const; /** * Transforms the passed absolute path to a relative resource path if * possible. * * @returns The relative path if it could be generated, QString() otherwise. */ QString getRelativeResourcePath(const QString& path) const; QString tempFilenameForFilename(const QString &filename) const; QString filenameForTempFilename(const QString &tempFilename) const; bool checkDownloadRestriction() const; DownloadJob* getJobByReply(QNetworkReply *r); /** Start a new download specified by the passed DownloadJob */ bool download(DownloadJob* job); /** Parses upstream Contents file and build checksum map. */ bool parseContents(DownloadJob *job); /** Compares the checksum of the file in filename with the contents map in * the passed DownloadJob */ bool checksumMatches(DownloadJob *job, const QString& filename) const; - bool registerResource(const QString& filename); + bool registerResourceAbsolute(const QString& filename); /** Unregisters the passed resource * * Caller must lock rcMutex. */ void unregisterResource_locked(const QString& filename); bool isRegistered(const QString& filename) const; QStringList getLocalResources(); private slots: /** Handle a finished download. * * Called whenever a single download (sub-job) has finished. Responsible * for iterating over possibly remaining sub-jobs of our DownloadJob. */ void downloadFinished(); void downloadReadyRead(); void handleError(QNetworkReply::NetworkError code); public: // public interface: /** * Possible return codes of a finished download */ enum DownloadFinishedCode { Success = 0, /**< Download finished successfully */ Error = 1, /**< Download error */ NoChange = 2 /**< Local files are up-to-date, no download was needed */ }; virtual ~DownloadManager(); /** * Registers DownloadManager singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static DownloadManager* getInstance(); /** * Generates a relative voices resources file-path for a given @p locale. * * @param locale Locale name string of the form \_\. * * @returns A relative voices resource path. */ Q_INVOKABLE QString getVoicesResourceForLocale(const QString& locale) const; /** * Checks whether the given relative resource @p path exists locally. * * @param path A relative resource path. */ Q_INVOKABLE bool haveLocalResource(const QString& path) const; /** * Whether any download is currently running. */ Q_INVOKABLE bool downloadIsRunning() const; /** * Whether the passed relative @p data is registered. * * For example, if you have a resource file which loads files under the * 'words' path like 'words/one.png'. You can call this method with 'words/one.png' * or with 'words'. * * @param data Relative resource path (file or directory). * * @sa areVoicesRegistered */ Q_INVOKABLE bool isDataRegistered(const QString& data) const; /** * Whether voices for the currently active locale are registered. * * @sa isDataRegistered */ Q_INVOKABLE bool areVoicesRegistered() const; + /* + * Registers a rcc resource file given by a relative resource path + * + * @param filename Relative resource path. + */ + Q_INVOKABLE bool registerResource(const QString& filename); + public slots: /** * Updates a resource @p path from the upstream server unless prohibited * by the settings and registers it if possible. * * If not prohibited by the setting 'isAutomaticDownloadsEnabled' checks * for an available upstream resource specified by @p path. * If the corresponding local resource does not exist or is out of date, * the resource is downloaded and registered. * * If automatic downloads/updates are prohibited and a local copy of the * specified resource exists, it is registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool updateResource(const QString& path); /** * Download a resource specified by the relative resource @p path and * register it if possible. * * If a corresponding local resource exists, an update will only be * downloaded if it is not up-to-date according to checksum comparison. * Whenever at the end we have a valid .rcc file it will be registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool downloadResource(const QString& path); /** * Shutdown DownloadManager instance. * * Aborts all currently running downloads. */ Q_INVOKABLE void shutdown(); /** * Abort all currently running downloads. */ Q_INVOKABLE void abortDownloads(); #if 0 Q_INVOKABLE bool checkForUpdates(); // might be helpful later with other use-cases! Q_INVOKABLE void registerLocalResources(); #endif signals: /** Emitted when a download error occurs. * * @param code enum NetworkError code. * @param msg Error string. */ void error(int code, const QString& msg); /** Emitted when a download has started. * * @param resource Relative resource path of the started download. */ void downloadStarted(const QString& resource); /** Emitted during a running download. * * All values refer to the currently active sub-job. * * @param bytesReceived Downloaded bytes. * @param bytesTotal Total bytes to download. */ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); /** Emitted when a download has finished. * * Also emitted in error cases. * * @param code DownloadFinishedCode. FIXME: when using DownloadFinishedCode * instead of int the code will not be passed to the QML layer, * use QENUMS? */ void downloadFinished(int code); /** Emitted when a resource has been registered. * * @param resource Relative resource path of the registered resource. * * @sa voicesRegistered */ void resourceRegistered(const QString& resource); /** Emitted when voices resources for current locale have been registered. * * Convenience signal and special case of resourceRegistered. * * @sa resourceRegistered */ void voicesRegistered(); }; #endif /* DOWNLOADMANAGER_H */ diff --git a/src/core/GCComboBox.qml b/src/core/GCComboBox.qml index 88fbe5da0..fbf9cf438 100644 --- a/src/core/GCComboBox.qml +++ b/src/core/GCComboBox.qml @@ -1,309 +1,310 @@ /* GCompris - GCComboBox.qml * * Copyright (C) 2015 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.2 import QtQuick.Controls 1.0 import GCompris 1.0 /** * A QML component unifying comboboxes in GCompris. * @ingroup components * * GCComboBox contains a combobox and a label. * When the combobox isn't active, it is displayed as a button containing the current value * and the combobox label (its description). * Once the button is clicked, the list of all available choices is displayed. * Also, above the list is the combobox label. * * Navigation can be done with keys and mouse/gestures. * As Qt comboboxes, you can either have a js Array or a Qml model as model. * GCComboBox should now be used wherever you'd use a QtQuick combobox. It has * been decided to implement comboboxes ourselves in GCompris because of * some integration problems on some OSes (native dialogs unavailable). */ Item { id: gccombobox focus: true width: parent.width height: flow.height /** * type:Item * Where the list containing all choices will be displayed. * Should be the dialogActivityConfig item if used on config. */ property Item background /** * type:int * Current index of the combobox. */ property int currentIndex: -1 /** * type:string * Current text displayed in the combobox when inactive. */ property string currentText /** * type:alias * Model for the list (user has to specify one). */ property alias model: gridview.model /** * type:string * Text besides the combobox, used to describe what the combobox is for. */ property string label /** * type:bool * Internal value. * If model is an js Array, we access data using modelData and [] else qml Model, we need to use model and get(). */ readonly property bool isModelArray: model.constructor === Array // start and stop trigs the animation signal start signal stop // emitted at stop animation end signal close onCurrentIndexChanged: { currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } /** * type:Flow * Combobox display when inactive: the button with current choice and its label besides. */ Flow { id: flow width: parent.width spacing: 5 * ApplicationInfo.ratio Rectangle { id: button visible: true // Add radius to add some space between text and borders implicitWidth: Math.max(200, currentTextBox.width+radius) implicitHeight: 50 * ApplicationInfo.ratio border.width: 2 border.color: "black" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: mouseArea.pressed ? "#87ff5c" : "#ffe85c" } GradientStop { position: 1 ; color: mouseArea.pressed ? "#44ff00" : "#f8d600" } } // Current value of combobox GCText { id: currentTextBox anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: currentText fontSize: mediumSize } MouseArea { id: mouseArea anchors.fill: parent onReleased: { popup.visible = true } } } Item { width: button.width height: parent.height GCText { id: labelText text: label anchors.verticalCenter: parent.verticalCenter fontSize: mediumSize wrapMode: Text.WordWrap } } } /** * type:Item * Combobox display when active: header with the description and the gridview containing all the available choices. */ Item { id: popup visible: false width: parent.width height: parent.height parent: background focus: visible // Forward event to activity if key pressed is not one of the handled key // (ctrl+F should still resize the window for example) Keys.onPressed: background.currentActivity.Keys.onPressed(event) Keys.onRightPressed: gridview.moveCurrentIndexRight(); Keys.onLeftPressed: gridview.moveCurrentIndexLeft(); Keys.onDownPressed: gridview.moveCurrentIndexDown(); Keys.onUpPressed: gridview.moveCurrentIndexUp(); Keys.onEscapePressed: { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); } Keys.onEnterPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onReturnPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onSpacePressed: { acceptChange(); hidePopUpAndRestoreFocus(); } // Don't accept the list value, restore previous value function discardChange() { if(isModelArray) { for(var i = 0 ; i < model.count ; ++ i) { if(model[currentIndex].text === currentText) { currentIndex = i; break; } } } else { for(var i = 0 ; i < model.length ; ++ i) { if(model.get(currentIndex).text === currentText) { currentIndex = i; break; } } } gridview.currentIndex = currentIndex; } // Accept the change. Updates the currentIndex and text of the button function acceptChange() { currentIndex = gridview.currentIndex; currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } function hidePopUpAndRestoreFocus() { popup.visible = false; // Restore focus on previous activity for keyboard input background.currentActivity.forceActiveFocus(); } Rectangle { id: listBackground anchors.fill: parent radius: 10 color: "grey" Rectangle { id : headerDescription z: 10 width: gridview.width height: gridview.elementHeight GCText { text: label fontSize: mediumSize wrapMode: Text.WordWrap anchors.horizontalCenter: parent.horizontalCenter } GCButtonCancel { id: discardIcon anchors.right: headerDescription.right anchors.top: headerDescription.top MouseArea { anchors.fill: parent onClicked: { popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } GridView { id: gridview - z: 4 + z: 4 readonly property int elementHeight: 40 * ApplicationInfo.ratio - // each element has a 300 width size minimum. If the screen is larger than it, we do a grid with cases with 300px for width at minimum. + // each element has a 300 width size minimum. If the screen is larger than it, + // we do a grid with cases with 300px for width at minimum. // If you have a better idea/formula to have a different column number, don't hesitate, change it :). - readonly property int numberOfColumns: Math.max(1, Math.floor(width / 300)) + readonly property int numberOfColumns: Math.max(1, Math.floor(width / (300 * ApplicationInfo.ratio))) contentHeight: isModelArray ? elementHeight*model.count/numberOfColumns : elementHeight*model.length/numberOfColumns width: listBackground.width height: listBackground.height-headerDescription.height currentIndex: gccombobox.currentIndex flickableDirection: Flickable.VerticalFlick clip: true anchors.top: headerDescription.bottom cellWidth: width / numberOfColumns cellHeight: elementHeight delegate: Component { Rectangle { width: gridview.cellWidth height: gridview.elementHeight color: GridView.isCurrentItem ? "darkcyan" : "beige" border.width: GridView.isCurrentItem ? 3 : 2 radius: 5 Image { id: isSelectedIcon visible: parent.GridView.isCurrentItem source: "qrc:/gcompris/src/core/resource/apply.svg" - anchors.right: textValue.left + anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - anchors.rightMargin: 10 - sourceSize.width: (gridview.elementHeight*0.8) * ApplicationInfo.ratio + anchors.leftMargin: 10 + sourceSize.width: (gridview.elementHeight * 0.8) } GCText { id: textValue text: isModelArray ? modelData.text : model.text anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { currentIndex = index popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } } } } } diff --git a/src/core/GCText.qml b/src/core/GCText.qml index 8c9db6e2f..37d11818c 100644 --- a/src/core/GCText.qml +++ b/src/core/GCText.qml @@ -1,125 +1,131 @@ /* GCompris - GCText.qml * * Copyright (C) 2014 Johnny Jazeix * * Authors: * Johnny Jazeix * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.0 import GCompris 1.0 import "." /** * A QML component unifying text presentation in GCompris. * @ingroup components * * GCText wraps QtQuick's Text to provide * * (1) a uniform font-family,
* (2) DPI based automatic font-scaling and
* (3) application-wide manual font-sizing.
* * Activities should almost always use the @ref fontSize property to * define font sizes, it implements both (2) and (3). Do not set * @c font.pointSize directly, or GCText's automatic scaling will be * deactivated. If you really need to enforce a fixed font size (which * might be the case for dialogs) use the fixFontSize property. * * The xxxSize properties are meant to be enum-like values for often needed * font sizes. (QtQuick does not support enum definitions on QML layer, cf. * Qt issue #14861.) * * @inherit QtQuick.Text * @sa GCSingletonFontLoader */ Text { /** * type:int * Tiny font-size. * Value: 10 */ readonly property int tinySize: 10.0 /** * type:int * Small font-size. * Value: 12 */ readonly property int smallSize: 12.0 /** * type:int * Regular font-size. * Value: 14 */ readonly property int regularSize: 14.0 /** * type:int * Medium font-size. * Value: 16 */ readonly property int mediumSize: 16.0 /** * type:int * Large font-size. * Value: 24 */ readonly property int largeSize: 24.0 /** * type:int * Huge font-size. * Value: 32 */ readonly property int hugeSize: 32.0 /** * type:bool * Whether to not apply ApplicationSettings.baseFontSize. * * Set to true if you don't want to scale fonts. Used in some rare cases * like for dialogs. */ property bool fixFontSize: false /** * type:int * Font size wrapping font.pointSize and applying font-scaling. * * Font-sizes are best specified using the fontSize property, which * wraps font.pointSize to take the DPI of the display into account. * If font.pointSize is used directly text might appear to small on some * devices. * * If you really need to specify font.pixelSize instead of pointSize, * you need to clear font.pointSize explicitly with NaN: * * @code * font.pointSize: NaN * @endcode * * Default is 14. */ property int fontSize: 14 + /** + * type:int + * Read only value of the calculated pointSize + */ + readonly property int pointSize: font.pointSize + font.pointSize: ((fixFontSize ? 0 : ApplicationSettings.baseFontSize) + fontSize) * ApplicationInfo.fontRatio font.family: GCSingletonFontLoader.fontLoader.name font.capitalization: ApplicationSettings.fontCapitalization }