mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Add four years of German listening, reading and writing beside the textbook.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3b4e7b283a
commit
b822c57875
31 changed files with 3589 additions and 102 deletions
136
src/english.c
136
src/english.c
|
|
@ -17,28 +17,6 @@
|
|||
#define EN_YEARS 4
|
||||
#define EN_N 8
|
||||
|
||||
typedef struct {
|
||||
const NetSlide *slides;
|
||||
int n_slides;
|
||||
const char *reading;
|
||||
const ChoiceQ *readq;
|
||||
const char **read_hints;
|
||||
int n_read;
|
||||
const char *listening;
|
||||
const ChoiceQ *listenq;
|
||||
const char **listen_hints;
|
||||
int n_listen;
|
||||
const TypedQ *gaps;
|
||||
int n_gaps;
|
||||
const char *write_prompt;
|
||||
const char *write_model;
|
||||
const char *write_keys;
|
||||
int write_min;
|
||||
const ChoiceQ *quiz;
|
||||
const char **quiz_hints;
|
||||
int n_quiz;
|
||||
} EnLesson;
|
||||
|
||||
#include "english_lessons.inc"
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -51,6 +29,7 @@ typedef struct {
|
|||
int year;
|
||||
int index;
|
||||
const EnLesson *lesson;
|
||||
void (*on_done)(int year, int index);
|
||||
GtkWidget *feedback;
|
||||
GtkWidget *model;
|
||||
GArray *picks;
|
||||
|
|
@ -69,6 +48,7 @@ typedef struct {
|
|||
gboolean heard;
|
||||
double speed;
|
||||
double gain;
|
||||
const char *voice;
|
||||
GPid pid;
|
||||
guint timer;
|
||||
guint tick_ms;
|
||||
|
|
@ -646,7 +626,7 @@ GtkWidget *build_enmap_page(int year) {
|
|||
/* Slides */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void en_slide_apply(NetLesson *L) {
|
||||
void skill_slide_apply(NetLesson *L) {
|
||||
char name[16];
|
||||
|
||||
if (!L || !L->stack)
|
||||
|
|
@ -705,7 +685,7 @@ static void en_slide_prev(GtkButton *button, gpointer data) {
|
|||
(void)button;
|
||||
if (L && L->idx > 0) {
|
||||
L->idx--;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,7 +697,7 @@ static void en_slide_next(GtkButton *button, gpointer data) {
|
|||
return;
|
||||
if (L->idx + 1 < L->n_slides) {
|
||||
L->idx++;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
} else {
|
||||
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
|
||||
}
|
||||
|
|
@ -726,12 +706,12 @@ static void en_slide_next(GtkButton *button, gpointer data) {
|
|||
void en_lessons_apply_lang(void) {
|
||||
for (int y = 0; y < EN_YEARS; y++)
|
||||
for (int i = 0; i < EN_N; i++)
|
||||
en_slide_apply(&en_rt[y][i]);
|
||||
skill_slide_apply(&en_rt[y][i]);
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_unit_page(int year, int index) {
|
||||
NetLesson *L = &en_rt[year][index];
|
||||
const EnLesson *lesson = &en_lessons[year][index];
|
||||
GtkWidget *skill_unit_page(NetLesson *L, const EnLesson *lesson,
|
||||
const char *back, const char *title_key,
|
||||
const char *sub_key) {
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
GtkWidget *nav;
|
||||
|
|
@ -746,8 +726,7 @@ static GtkWidget *build_en_unit_page(int year, int index) {
|
|||
gtk_widget_set_margin_top(page, 20);
|
||||
gtk_widget_set_margin_bottom(page, 20);
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar(en_map_page[year], en_title_key[year][index],
|
||||
en_sub_key[year][index]));
|
||||
top_bar(back, title_key, sub_key));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
@ -795,7 +774,7 @@ static GtkWidget *build_en_unit_page(int year, int index) {
|
|||
}
|
||||
en_rescale_notes(0);
|
||||
net_notes_target = NULL;
|
||||
en_slide_apply(L);
|
||||
skill_slide_apply(L);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
|
@ -1054,14 +1033,58 @@ static void en_mac_levels(EnEx *ctx) {
|
|||
ctx->synth, sel_registerName("setVolume:"), vol);
|
||||
}
|
||||
|
||||
static gboolean en_mac_ensure(EnEx *ctx) {
|
||||
static const char *voices[] = {
|
||||
static gboolean en_mac_try_voice(id synth, const char *name) {
|
||||
return ((BOOL (*)(id, SEL, id))objc_msgSend)(
|
||||
synth, sel_registerName("setVoice:"), en_ns(name));
|
||||
}
|
||||
|
||||
static void en_mac_choose(id synth, const char *lang) {
|
||||
static const char *en_voices[] = {
|
||||
"com.apple.voice.compact.en-GB.Daniel",
|
||||
"com.apple.speech.synthesis.voice.daniel",
|
||||
"com.apple.voice.compact.en-US.Samantha",
|
||||
"com.apple.speech.synthesis.voice.samantha",
|
||||
NULL
|
||||
};
|
||||
static const char *de_voices[] = {
|
||||
"com.apple.voice.compact.de-DE.Anna",
|
||||
"com.apple.speech.synthesis.voice.anna",
|
||||
"com.apple.voice.compact.de-DE.Helena",
|
||||
"com.apple.speech.synthesis.voice.petra",
|
||||
NULL
|
||||
};
|
||||
gboolean german = lang && g_str_has_prefix(lang, "de");
|
||||
const char **list = german ? de_voices : en_voices;
|
||||
const char *needle = german ? "de-DE" : "en-GB";
|
||||
Class cls;
|
||||
id voices;
|
||||
unsigned long n;
|
||||
|
||||
for (int i = 0; list[i]; i++) {
|
||||
if (en_mac_try_voice(synth, list[i]))
|
||||
return;
|
||||
}
|
||||
cls = objc_getClass("NSSpeechSynthesizer");
|
||||
if (!cls)
|
||||
return;
|
||||
voices = ((id (*)(Class, SEL))objc_msgSend)(
|
||||
cls, sel_registerName("availableVoices"));
|
||||
if (!voices)
|
||||
return;
|
||||
n = ((unsigned long (*)(id, SEL))objc_msgSend)(
|
||||
voices, sel_registerName("count"));
|
||||
for (unsigned long i = 0; i < n; i++) {
|
||||
id name = ((id (*)(id, SEL, unsigned long))objc_msgSend)(
|
||||
voices, sel_registerName("objectAtIndex:"), i);
|
||||
const char *utf = ((const char *(*)(id, SEL))objc_msgSend)(
|
||||
name, sel_registerName("UTF8String"));
|
||||
|
||||
if (utf && strstr(utf, needle) && en_mac_try_voice(synth, utf))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean en_mac_ensure(EnEx *ctx) {
|
||||
Class cls;
|
||||
id obj;
|
||||
|
||||
|
|
@ -1075,11 +1098,7 @@ static gboolean en_mac_ensure(EnEx *ctx) {
|
|||
if (!obj)
|
||||
return FALSE;
|
||||
ctx->synth = obj;
|
||||
for (int i = 0; voices[i]; i++) {
|
||||
if (((BOOL (*)(id, SEL, id))objc_msgSend)(
|
||||
obj, sel_registerName("setVoice:"), en_ns(voices[i])))
|
||||
break;
|
||||
}
|
||||
en_mac_choose(obj, ctx->voice);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -1163,14 +1182,16 @@ static gboolean en_launch(EnEx *ctx) {
|
|||
g_snprintf(rate, sizeof rate, "%d", (int)(160.0 * speed));
|
||||
g_snprintf(amp, sizeof amp, "%d", (int)(ctx->gain * 200.0));
|
||||
{
|
||||
char *a[] = {"espeak-ng", "-v", "en", "-s", rate, "-a", amp, text, NULL};
|
||||
char *b[] = {"espeak", "-v", "en", "-s", rate, "-a", amp, text, NULL};
|
||||
const char *voice = ctx->voice && ctx->voice[0] ? ctx->voice : "en";
|
||||
char *a[] = {"espeak-ng", "-v", (char *)voice, "-s", rate, "-a", amp, text, NULL};
|
||||
char *b[] = {"espeak", "-v", (char *)voice, "-s", rate, "-a", amp, text, NULL};
|
||||
#ifdef __APPLE__
|
||||
char *c[] = {"say", "-r", rate, text, NULL};
|
||||
char **tries[] = {c};
|
||||
char *c_en[] = {"say", "-r", rate, text, NULL};
|
||||
char *c_de[] = {"say", "-v", "Anna", "-r", rate, text, NULL};
|
||||
char **tries[] = {g_str_has_prefix(voice, "de") ? c_de : c_en};
|
||||
int n = 1;
|
||||
#else
|
||||
char *c[] = {"spd-say", "-l", "en", "-r", rate, text, NULL};
|
||||
char *c[] = {"spd-say", "-l", (char *)voice, "-r", rate, text, NULL};
|
||||
char **tries[] = {a, b, c};
|
||||
int n = 3;
|
||||
#endif
|
||||
|
|
@ -1416,7 +1437,8 @@ static void en_check(GtkButton *button, gpointer data) {
|
|||
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
|
||||
if (ctx->model)
|
||||
gtk_widget_set_visible(ctx->model, TRUE);
|
||||
mark_en_done(ctx->year, ctx->index);
|
||||
if (ctx->on_done)
|
||||
ctx->on_done(ctx->year, ctx->index);
|
||||
} else if (ctx->essay && !write_ok &&
|
||||
mcq_ok == (int)ctx->picks->len &&
|
||||
gap_ok == ctx->lesson->n_gaps) {
|
||||
|
|
@ -1426,8 +1448,10 @@ static void en_check(GtkButton *button, gpointer data) {
|
|||
}
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_ex_page(int year, int index) {
|
||||
const EnLesson *lesson = &en_lessons[year][index];
|
||||
GtkWidget *skill_ex_page(const EnLesson *lesson, int year, int index,
|
||||
void (*on_done)(int, int),
|
||||
const char *back, const char *title_key,
|
||||
const char *voice) {
|
||||
EnEx *ctx = g_new0(EnEx, 1);
|
||||
GtkWidget *page;
|
||||
GtkWidget *scroll;
|
||||
|
|
@ -1437,6 +1461,8 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
ctx->year = year;
|
||||
ctx->index = index;
|
||||
ctx->lesson = lesson;
|
||||
ctx->on_done = on_done;
|
||||
ctx->voice = voice && voice[0] ? voice : "en";
|
||||
ctx->picks = g_array_new(FALSE, FALSE, sizeof(EnPick));
|
||||
ctx->entries = g_array_new(FALSE, FALSE, sizeof(GtkWidget *));
|
||||
ctx->gap_hints = g_array_new(FALSE, TRUE, sizeof(GtkWidget *));
|
||||
|
|
@ -1446,9 +1472,7 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar(en_unit_page[year][index],
|
||||
en_title_key[year][index], NULL));
|
||||
gtk_box_append(GTK_BOX(page), top_bar(back, title_key, NULL));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
@ -1586,6 +1610,18 @@ static GtkWidget *build_en_ex_page(int year, int index) {
|
|||
return page;
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_unit_page(int year, int index) {
|
||||
return skill_unit_page(&en_rt[year][index], &en_lessons[year][index],
|
||||
en_map_page[year], en_title_key[year][index],
|
||||
en_sub_key[year][index]);
|
||||
}
|
||||
|
||||
static GtkWidget *build_en_ex_page(int year, int index) {
|
||||
return skill_ex_page(&en_lessons[year][index], year, index, mark_en_done,
|
||||
en_unit_page[year][index], en_title_key[year][index],
|
||||
"en");
|
||||
}
|
||||
|
||||
void add_en_pages(GtkStack *stack) {
|
||||
en_prepare();
|
||||
for (int y = 0; y < EN_YEARS; y++) {
|
||||
|
|
|
|||
234
src/german_course.c
Normal file
234
src/german_course.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#include "graduately.h"
|
||||
|
||||
/* Skill course next to the three textbook units. Four years, same
|
||||
* listening / reading / writing player as English. */
|
||||
|
||||
#define DE_YEARS 4
|
||||
#define DE_N 6
|
||||
|
||||
#include "german_lessons.inc"
|
||||
|
||||
static NetLesson de_rt[DE_YEARS][DE_N];
|
||||
static char de_map_page[DE_YEARS][16];
|
||||
static char de_unit_page[DE_YEARS][DE_N][16];
|
||||
static char de_ex_page[DE_YEARS][DE_N][16];
|
||||
static char de_title_key[DE_YEARS][DE_N][16];
|
||||
static char de_sub_key[DE_YEARS][DE_N][20];
|
||||
static gboolean de_ready;
|
||||
|
||||
static void de_prepare(void) {
|
||||
if (de_ready)
|
||||
return;
|
||||
de_ready = TRUE;
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
g_snprintf(de_map_page[y], sizeof de_map_page[y], "de%dmap", y + 1);
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
g_snprintf(de_unit_page[y][i], sizeof de_unit_page[y][i],
|
||||
"de%dunit%d", y + 1, i + 1);
|
||||
g_snprintf(de_ex_page[y][i], sizeof de_ex_page[y][i],
|
||||
"de%dex%d", y + 1, i + 1);
|
||||
g_snprintf(de_title_key[y][i], sizeof de_title_key[y][i],
|
||||
"de_y%d_u%d", y + 1, i + 1);
|
||||
g_snprintf(de_sub_key[y][i], sizeof de_sub_key[y][i],
|
||||
"de_y%d_u%d_sub", y + 1, i + 1);
|
||||
de_rt[y][i].unit_page = de_unit_page[y][i];
|
||||
de_rt[y][i].ex_page = de_ex_page[y][i];
|
||||
de_rt[y][i].n_slides = (guint)de_lessons[y][i].n_slides;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void de_save_progress(void) {
|
||||
GKeyFile *kf = g_key_file_new();
|
||||
gchar *data;
|
||||
GError *err = NULL;
|
||||
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d-%d", y + 1, i + 1);
|
||||
g_key_file_set_boolean(kf, "done", key, de_rt[y][i].done);
|
||||
}
|
||||
}
|
||||
data = g_key_file_to_data(kf, NULL, NULL);
|
||||
if (!g_file_set_contents(PROGRESS_DE, data, -1, &err)) {
|
||||
g_warning("german course: %s", err->message);
|
||||
g_error_free(err);
|
||||
}
|
||||
g_free(data);
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
void de_load_progress(void) {
|
||||
GKeyFile *kf = g_key_file_new();
|
||||
GError *err = NULL;
|
||||
|
||||
de_prepare();
|
||||
if (!g_key_file_load_from_file(kf, PROGRESS_DE, G_KEY_FILE_NONE, &err)) {
|
||||
if (err)
|
||||
g_error_free(err);
|
||||
g_key_file_free(kf);
|
||||
return;
|
||||
}
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char key[8];
|
||||
|
||||
g_snprintf(key, sizeof key, "%d-%d", y + 1, i + 1);
|
||||
de_rt[y][i].done = g_key_file_get_boolean(kf, "done", key, NULL);
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
static void mark_de_done(int year, int index) {
|
||||
if (year < 0 || year >= DE_YEARS || index < 0 || index >= DE_N)
|
||||
return;
|
||||
if (de_rt[year][index].done)
|
||||
return;
|
||||
de_rt[year][index].done = TRUE;
|
||||
de_save_progress();
|
||||
refresh_stats_ui();
|
||||
}
|
||||
|
||||
void progress_for_de(ProgressSum *out) {
|
||||
out->total_ex += DE_YEARS * DE_N;
|
||||
out->open_units += DE_YEARS * DE_N;
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
if (de_rt[y][i].done) {
|
||||
out->done_ex++;
|
||||
out->done_units++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void de_lessons_apply_lang(void) {
|
||||
for (int y = 0; y < DE_YEARS; y++)
|
||||
for (int i = 0; i < DE_N; i++)
|
||||
skill_slide_apply(&de_rt[y][i]);
|
||||
}
|
||||
|
||||
static GtkWidget *de_nav_button(const char *title_key, const char *sub_key,
|
||||
const char *target, const char *badge) {
|
||||
GtkWidget *btn = gtk_button_new();
|
||||
GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||
GtkWidget *num = gtk_label_new(badge);
|
||||
GtkWidget *texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||
GtkWidget *title = gtk_label_new(NULL);
|
||||
GtkWidget *sub = gtk_label_new(NULL);
|
||||
|
||||
gtk_widget_add_css_class(btn, "unit-node");
|
||||
gtk_widget_add_css_class(btn, "current");
|
||||
gtk_widget_set_hexpand(btn, TRUE);
|
||||
gtk_widget_set_margin_start(row, 18);
|
||||
gtk_widget_set_margin_end(row, 18);
|
||||
gtk_widget_set_margin_top(row, 10);
|
||||
gtk_widget_set_margin_bottom(row, 10);
|
||||
gtk_button_set_child(GTK_BUTTON(btn), row);
|
||||
gtk_widget_add_css_class(num, "unit-number");
|
||||
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
|
||||
gtk_box_append(GTK_BOX(row), num);
|
||||
gtk_widget_set_hexpand(texts, TRUE);
|
||||
gtk_box_append(GTK_BOX(row), texts);
|
||||
i18n_bind(title, title_key, 0);
|
||||
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(title, "unit-name");
|
||||
gtk_box_append(GTK_BOX(texts), title);
|
||||
i18n_bind(sub, sub_key, 0);
|
||||
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||
gtk_label_set_xalign(GTK_LABEL(sub), 0);
|
||||
gtk_widget_add_css_class(sub, "quiz-intro");
|
||||
gtk_box_append(GTK_BOX(texts), sub);
|
||||
g_object_set_data_full(G_OBJECT(btn), "target", g_strdup(target), g_free);
|
||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||
return btn;
|
||||
}
|
||||
|
||||
static GtkWidget *de_list_page(const char *back, const char *title,
|
||||
const char *sub, GtkWidget *list) {
|
||||
GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget *scroll = gtk_scrolled_window_new();
|
||||
|
||||
gtk_widget_set_hexpand(page, TRUE);
|
||||
gtk_widget_set_vexpand(page, TRUE);
|
||||
gtk_widget_set_margin_start(page, 32);
|
||||
gtk_widget_set_margin_end(page, 32);
|
||||
gtk_widget_set_margin_top(page, 24);
|
||||
gtk_widget_set_margin_bottom(page, 24);
|
||||
gtk_box_append(GTK_BOX(page), top_bar(back, title, sub));
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_widget_set_vexpand(scroll, TRUE);
|
||||
gtk_widget_set_margin_top(scroll, 18);
|
||||
gtk_box_append(GTK_BOX(page), scroll);
|
||||
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_size_request(list, 520, -1);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||
return page;
|
||||
}
|
||||
|
||||
GtkWidget *build_dehome_page(void) {
|
||||
GtkWidget *list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
static const char *years[] = {
|
||||
"de_year1", "de_year2", "de_year3", "de_year4",
|
||||
};
|
||||
static const char *subs[] = {
|
||||
"de_y1_sub", "de_y2_sub", "de_y3_sub", "de_y4_sub",
|
||||
};
|
||||
|
||||
de_prepare();
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button("de_book_title", "de_book_sub", "roadmap", "U"));
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
char badge[8];
|
||||
|
||||
g_snprintf(badge, sizeof badge, "%d", y + 1);
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button(years[y], subs[y], de_map_page[y], badge));
|
||||
}
|
||||
return de_list_page("subjects", "Deutsch", "de_home_sub", list);
|
||||
}
|
||||
|
||||
static GtkWidget *build_de_map(int year) {
|
||||
GtkWidget *list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
static const char *year_keys[] = {
|
||||
"de_year1", "de_year2", "de_year3", "de_year4",
|
||||
};
|
||||
static const char *sub_keys[] = {
|
||||
"de_y1_sub", "de_y2_sub", "de_y3_sub", "de_y4_sub",
|
||||
};
|
||||
|
||||
de_prepare();
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
char badge[8];
|
||||
|
||||
g_snprintf(badge, sizeof badge, "%d", i + 1);
|
||||
gtk_box_append(GTK_BOX(list),
|
||||
de_nav_button(de_title_key[year][i],
|
||||
de_sub_key[year][i],
|
||||
de_unit_page[year][i], badge));
|
||||
}
|
||||
return de_list_page("dehome", year_keys[year], sub_keys[year], list);
|
||||
}
|
||||
|
||||
void add_de_pages(GtkStack *stack) {
|
||||
de_prepare();
|
||||
for (int y = 0; y < DE_YEARS; y++) {
|
||||
gtk_stack_add_named(stack, build_de_map(y), de_map_page[y]);
|
||||
for (int i = 0; i < DE_N; i++) {
|
||||
gtk_stack_add_named(stack,
|
||||
skill_unit_page(&de_rt[y][i], &de_lessons[y][i],
|
||||
de_map_page[y], de_title_key[y][i],
|
||||
de_sub_key[y][i]),
|
||||
de_unit_page[y][i]);
|
||||
gtk_stack_add_named(stack,
|
||||
skill_ex_page(&de_lessons[y][i], y, i, mark_de_done,
|
||||
de_unit_page[y][i], de_title_key[y][i], "de"),
|
||||
de_ex_page[y][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/german_i18n.inc
Normal file
60
src/german_i18n.inc
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{"de_home_sub", "Učebnice a čtyři ročníky s poslechem, čtením a psaním.", "The textbook plus four years of listening, reading and writing."},
|
||||
{"de_book_title", "Učebnice", "Textbook"},
|
||||
{"de_book_sub", "Neue Freunde, Aus aller Welt a Bei uns zu Hause.", "Neue Freunde, Aus aller Welt and Bei uns zu Hause."},
|
||||
{"de_years_title", "Ročníky", "School years"},
|
||||
{"de_y1_sub", "Pozdravy, rodina, škola, jídlo a den. Úroveň A1.", "Greetings, family, school, food and the day. Level A1."},
|
||||
{"de_year1", "1. ročník", "Year 1"},
|
||||
{"de_y1_u1", "Pozdravy a představení", "Greetings and introductions"},
|
||||
{"de_y1_u1_sub", "Sein, heißen a woher. Doplňování a kvíz.", "Sein, heißen and woher. Gaps and a quiz."},
|
||||
{"de_y1_u2", "Rodina", "Family"},
|
||||
{"de_y1_u2_sub", "Haben a přivlastňovací zájmena. Čtení a doplňování.", "Haben and possessives. Reading and gaps."},
|
||||
{"de_y1_u3", "Ve škole", "At school"},
|
||||
{"de_y1_u3_sub", "Přítomný čas. Poslech a doplňování.", "The present tense. Listening and gaps."},
|
||||
{"de_y1_u4", "Jídlo", "Food"},
|
||||
{"de_y1_u4_sub", "Möchten a 4. pád. Krátký dialog a kvíz.", "Möchten and the accusative. A short dialogue and a quiz."},
|
||||
{"de_y1_u5", "Můj den", "My day"},
|
||||
{"de_y1_u5_sub", "Čas a odlučitelné předpony. Čtení a doplňování.", "Time and separable verbs. Reading and gaps."},
|
||||
{"de_y1_u6", "Test 1. ročníku", "Year 1 test"},
|
||||
{"de_y1_u6_sub", "Čtení, poslech, gramatika A1 a krátké psaní.", "Reading, listening, A1 grammar and a short text."},
|
||||
{"de_y2_sub", "Město, nákupy, volný čas, bydlení a perfektum. Úroveň A2.", "Town, shopping, free time, home and the perfect. Level A2."},
|
||||
{"de_year2", "2. ročník", "Year 2"},
|
||||
{"de_y2_u1", "Ve městě", "In town"},
|
||||
{"de_y2_u1_sub", "Wo a wohin, předložky. Doplňování a kvíz.", "Wo and wohin, prepositions. Gaps and a quiz."},
|
||||
{"de_y2_u2", "Nákupy", "Shopping"},
|
||||
{"de_y2_u2_sub", "Ceny a 4. pád v obchodě. Čtení a kvíz.", "Prices and the accusative in a shop. Reading and a quiz."},
|
||||
{"de_y2_u3", "Volný čas", "Free time"},
|
||||
{"de_y2_u3_sub", "Modální slovesa. Poslech a doplňování.", "Modal verbs. Listening and gaps."},
|
||||
{"de_y2_u4", "Bydlení", "Home"},
|
||||
{"de_y2_u4_sub", "Místnosti a předložky. Popis bytu.", "Rooms and prepositions. A description of a flat."},
|
||||
{"de_y2_u5", "Perfektum", "The perfect tense"},
|
||||
{"de_y2_u5_sub", "Haben nebo sein a příčestí. Doplňování a kvíz.", "Haben or sein plus the participle. Gaps and a quiz."},
|
||||
{"de_y2_u6", "Test 2. ročníku", "Year 2 test"},
|
||||
{"de_y2_u6_sub", "Čtení, poslech, A2 a krátké psaní.", "Reading, listening, A2 and a short text."},
|
||||
{"de_y3_sub", "Zdraví, cestování, věty s weil, práce a srovnání. Úroveň A2+.", "Health, travel, weil-clauses, work and comparison. Level A2+."},
|
||||
{"de_year3", "3. ročník", "Year 3"},
|
||||
{"de_y3_u1", "U lékaře", "At the doctor's"},
|
||||
{"de_y3_u1_sub", "Tělo, tut a rozkaz. Čtení a kvíz.", "The body, tut and the imperative. Reading and a quiz."},
|
||||
{"de_y3_u2", "Cesta", "A trip"},
|
||||
{"de_y3_u2_sub", "Perfektum v vyprávění. Poslech a doplňování.", "The perfect in a story. Listening and gaps."},
|
||||
{"de_y3_u3", "Weil, dass, wenn", "Weil, dass, wenn"},
|
||||
{"de_y3_u3_sub", "Vedlejší věta a sloveso na konci. Doplňování a kvíz.", "Subordinate clauses and the verb at the end. Gaps and a quiz."},
|
||||
{"de_y3_u4", "Práce a vykání", "Work and Sie"},
|
||||
{"de_y3_u4_sub", "Formální věta. E-mail a kvíz.", "A formal sentence. An email and a quiz."},
|
||||
{"de_y3_u5", "Srovnání a příroda", "Comparison and nature"},
|
||||
{"de_y3_u5_sub", "Komparativ a superlativ. Čtení a doplňování.", "Comparative and superlative. Reading and gaps."},
|
||||
{"de_y3_u6", "Test 3. ročníku", "Year 3 test"},
|
||||
{"de_y3_u6_sub", "Čtení, poslech, weil a krátký názor.", "Reading, listening, weil and a short opinion."},
|
||||
{"de_y4_sub", "Média, formální dopis, čtení, konjunktiv a maturitní témata. Úroveň B1.", "Media, a formal letter, reading, the subjunctive and exam topics. Level B1."},
|
||||
{"de_year4", "4. ročník", "Year 4"},
|
||||
{"de_y4_u1", "Média", "The media"},
|
||||
{"de_y4_u1_sub", "Poslech rozhovoru a kvíz k slovům.", "An interview and a vocabulary quiz."},
|
||||
{"de_y4_u2", "Formální e-mail", "A formal email"},
|
||||
{"de_y4_u2_sub", "Stížnost nebo žádost. Psaní a kvíz.", "A complaint or a request. Writing and a quiz."},
|
||||
{"de_y4_u3", "Čtení: noční vlak", "Reading: the night train"},
|
||||
{"de_y4_u3_sub", "Delší text a otázky jako u maturity.", "A longer text and exam-style questions."},
|
||||
{"de_y4_u4", "Kdyby", "If I were"},
|
||||
{"de_y4_u4_sub", "Konjunktiv II. Doplňování a kvíz.", "Konjunktiv II. Gaps and a quiz."},
|
||||
{"de_y4_u5", "Témata k maturitě", "Exam topics"},
|
||||
{"de_y4_u5_sub", "Kolokace k běžným okruhům. Doplňování a kvíz.", "Collocations for common topics. Gaps and a quiz."},
|
||||
{"de_y4_u6", "Cvičná zkouška", "A practice paper"},
|
||||
{"de_y4_u6_sub", "Čtení, poslech, gramatika a krátký text.", "Reading, listening, grammar and a short text."},
|
||||
1453
src/german_lessons.inc
Normal file
1453
src/german_lessons.inc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -58,6 +58,7 @@ extern char *app_progress_on2;
|
|||
extern char *app_progress_on3;
|
||||
extern char *app_progress_on4;
|
||||
extern char *app_progress_en;
|
||||
extern char *app_progress_de;
|
||||
extern char *app_progress_mluvnice;
|
||||
extern char *app_progress_cetba;
|
||||
extern char *app_progress_lit;
|
||||
|
|
@ -87,6 +88,7 @@ extern char *app_progress_mat4;
|
|||
#define PROGRESS_ON3 app_progress_on3
|
||||
#define PROGRESS_ON4 app_progress_on4
|
||||
#define PROGRESS_EN app_progress_en
|
||||
#define PROGRESS_DE app_progress_de
|
||||
#define NODE_SIZE 88.0
|
||||
#define PATH_SPAC 240.0
|
||||
#define ROAD_MX 150.0 /* horizontal canvas margin */
|
||||
|
|
@ -622,6 +624,27 @@ typedef struct {
|
|||
const char *tip;
|
||||
const char *line[8];
|
||||
} NetSlide;
|
||||
typedef struct {
|
||||
const NetSlide *slides;
|
||||
int n_slides;
|
||||
const char *reading;
|
||||
const ChoiceQ *readq;
|
||||
const char **read_hints;
|
||||
int n_read;
|
||||
const char *listening;
|
||||
const ChoiceQ *listenq;
|
||||
const char **listen_hints;
|
||||
int n_listen;
|
||||
const TypedQ *gaps;
|
||||
int n_gaps;
|
||||
const char *write_prompt;
|
||||
const char *write_model;
|
||||
const char *write_keys;
|
||||
int write_min;
|
||||
const ChoiceQ *quiz;
|
||||
const char **quiz_hints;
|
||||
int n_quiz;
|
||||
} EnLesson;
|
||||
typedef struct {
|
||||
const ChoiceQ *qs;
|
||||
int n;
|
||||
|
|
@ -911,6 +934,18 @@ void progress_for_en(ProgressSum *out);
|
|||
void en_rail_theme_reset(void);
|
||||
GtkWidget *build_enyears_page(void);
|
||||
void add_en_pages(GtkStack *stack);
|
||||
GtkWidget *skill_unit_page(NetLesson *L, const EnLesson *lesson,
|
||||
const char *back, const char *title_key,
|
||||
const char *sub_key);
|
||||
GtkWidget *skill_ex_page(const EnLesson *lesson, int year, int index,
|
||||
void (*on_done)(int, int),
|
||||
const char *back, const char *title_key,
|
||||
const char *voice);
|
||||
void skill_slide_apply(NetLesson *L);
|
||||
GtkWidget *build_dehome_page(void);
|
||||
void add_de_pages(GtkStack *stack);
|
||||
void de_load_progress(void);
|
||||
void progress_for_de(ProgressSum *out);
|
||||
void on2_load_progress(void);
|
||||
void on2_save_progress(void);
|
||||
void mark_on2_done(int lesson_id);
|
||||
|
|
|
|||
|
|
@ -1620,6 +1620,7 @@ const TrEntry tr_ui[] = {
|
|||
{"lit_fuks_plot_sub", "Přetáhněte části příběhu do správného pořadí.",
|
||||
"Drag the story parts into the correct order."},
|
||||
#include "english_i18n.inc"
|
||||
#include "german_i18n.inc"
|
||||
#include "math_i18n.inc"
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
|
@ -2603,6 +2604,7 @@ void on2_lessons_apply_lang(void);
|
|||
void on3_lessons_apply_lang(void);
|
||||
void on4_lessons_apply_lang(void);
|
||||
void en_lessons_apply_lang(void);
|
||||
void de_lessons_apply_lang(void);
|
||||
void lit_lessons_apply_lang(void);
|
||||
void sci_lessons_apply_lang(void);
|
||||
void fyz_lessons_apply_lang(void);
|
||||
|
|
@ -2626,6 +2628,7 @@ void apply_language(void) {
|
|||
on3_lessons_apply_lang();
|
||||
on4_lessons_apply_lang();
|
||||
en_lessons_apply_lang();
|
||||
de_lessons_apply_lang();
|
||||
lit_lessons_apply_lang();
|
||||
sci_lessons_apply_lang();
|
||||
fyz_lessons_apply_lang();
|
||||
|
|
|
|||
|
|
@ -418,6 +418,8 @@ static gboolean run_smoke_test(gpointer data) {
|
|||
"on4unit1", "on4ex1", "on4unit16", "on4ex16",
|
||||
"enyears", "en1map", "en4map",
|
||||
"en1unit1", "en1ex1", "en4unit8", "en4ex8",
|
||||
"dehome", "de1map", "de4map",
|
||||
"de1unit1", "de1ex1", "de4unit6", "de4ex6",
|
||||
"unit1", "unit2", "unit3",
|
||||
"u1e1", "u1e13", "u2e1", "u2e19", "u3e1", "u3e15",
|
||||
"u1vocab", "u2vocab", "u3vocab",
|
||||
|
|
@ -523,6 +525,8 @@ void activate(GtkApplication *app, gpointer user_data) {
|
|||
gtk_stack_add_named(main_stack, welcome_page, "welcome");
|
||||
gtk_stack_add_named(main_stack, build_subjects_page(), "subjects");
|
||||
gtk_stack_add_named(main_stack, roadmap_page, "roadmap");
|
||||
gtk_stack_add_named(main_stack, build_dehome_page(), "dehome");
|
||||
add_de_pages(main_stack);
|
||||
gtk_stack_add_named(main_stack, build_stats_page(), "stats");
|
||||
gtk_stack_add_named(main_stack, build_netyears_page(), "netyears");
|
||||
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ void load_progress(void) {
|
|||
on3_load_progress();
|
||||
on4_load_progress();
|
||||
en_load_progress();
|
||||
de_load_progress();
|
||||
lit_load_progress();
|
||||
sci_load_progress();
|
||||
fyz_load_progress();
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ GtkWidget *build_roadmap_page(void) {
|
|||
gtk_widget_set_margin_bottom(page, 24);
|
||||
|
||||
gtk_box_append(GTK_BOX(page),
|
||||
top_bar("subjects", "roadmap_title", "roadmap_sub"));
|
||||
top_bar("dehome", "roadmap_title", "roadmap_sub"));
|
||||
|
||||
scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||
|
|
|
|||
13
src/search.c
13
src/search.c
|
|
@ -29,7 +29,7 @@ static gboolean subject_open(int i) {
|
|||
|
||||
static const char *subject_page(int i) {
|
||||
if (i == 0)
|
||||
return "roadmap";
|
||||
return "dehome";
|
||||
if (i == NET_SUBJ)
|
||||
return "netyears";
|
||||
if (i == HW_SUBJ)
|
||||
|
|
@ -248,6 +248,17 @@ static void catalog_rebuild(void) {
|
|||
catalog_add_key("en_year2", "search_lesson", "en2map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("en_year3", "search_lesson", "en3map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("en_year4", "search_lesson", "en4map", FALSE, 1, "anglictina english");
|
||||
catalog_add_key("de_book_title", "search_unit", "roadmap", FALSE, 1, "nemcina deutsch ucebnice");
|
||||
for (i = 1; i <= 4; i++) {
|
||||
g_snprintf(key, sizeof key, "de_year%d", i);
|
||||
g_snprintf(page, sizeof page, "de%dmap", i);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 1, "nemcina deutsch rocnik");
|
||||
for (n = 1; n <= 6; n++) {
|
||||
g_snprintf(key, sizeof key, "de_y%d_u%d", i, n);
|
||||
g_snprintf(page, sizeof page, "de%dunit%d", i, n);
|
||||
catalog_add_key(key, "search_lesson", page, FALSE, 2, "nemcina deutsch");
|
||||
}
|
||||
}
|
||||
for (int y = 1; y <= 4; y++) {
|
||||
for (i = 1; i <= 8; i++) {
|
||||
g_snprintf(key, sizeof(key), "en_y%d_u%d", y, i);
|
||||
|
|
|
|||
10
src/stats.c
10
src/stats.c
|
|
@ -83,8 +83,11 @@ void refresh_stats_ui(void) {
|
|||
progress_for_fyz(&all);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&all);
|
||||
else
|
||||
else {
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
|
||||
if (s == 0)
|
||||
progress_for_de(&all);
|
||||
}
|
||||
}
|
||||
|
||||
pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0;
|
||||
|
|
@ -138,8 +141,11 @@ void refresh_stats_ui(void) {
|
|||
progress_for_fyz(&sp);
|
||||
else if (s == MATH_SUBJ)
|
||||
progress_for_math(&sp);
|
||||
else
|
||||
else {
|
||||
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
|
||||
if (s == 0)
|
||||
progress_for_de(&sp);
|
||||
}
|
||||
if (has_content) {
|
||||
tmp = g_strdup_printf(tr("stats_ex_fmt"), sp.done_ex, sp.total_ex);
|
||||
gtk_label_set_text(GTK_LABEL(count), tmp);
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ static GtkWidget *subject_icon(int i) {
|
|||
|
||||
static const char *subject_target(int i) {
|
||||
if (i == 0)
|
||||
return "roadmap";
|
||||
return "dehome";
|
||||
if (i == NET_SUBJ)
|
||||
return "netyears";
|
||||
if (i == HW_SUBJ)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ char *app_progress_on2;
|
|||
char *app_progress_on3;
|
||||
char *app_progress_on4;
|
||||
char *app_progress_en;
|
||||
char *app_progress_de;
|
||||
char *app_progress_mluvnice;
|
||||
char *app_progress_cetba;
|
||||
char *app_progress_lit;
|
||||
|
|
@ -183,6 +184,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
g_free(app_progress_on3);
|
||||
g_free(app_progress_on4);
|
||||
g_free(app_progress_en);
|
||||
g_free(app_progress_de);
|
||||
g_free(app_progress_mluvnice);
|
||||
g_free(app_progress_cetba);
|
||||
g_free(app_progress_lit);
|
||||
|
|
@ -213,6 +215,7 @@ static void set_progress_paths(const char *write_root) {
|
|||
app_progress_on3 = g_build_filename(app_progress_dir, "on3.conf", NULL);
|
||||
app_progress_on4 = g_build_filename(app_progress_dir, "on4.conf", NULL);
|
||||
app_progress_en = g_build_filename(app_progress_dir, "english.conf", NULL);
|
||||
app_progress_de = g_build_filename(app_progress_dir, "german-course.conf", NULL);
|
||||
app_progress_mluvnice = g_build_filename(app_progress_dir, "mluvnice.conf", NULL);
|
||||
app_progress_cetba = g_build_filename(app_progress_dir, "cetba.conf", NULL);
|
||||
app_progress_lit = g_build_filename(app_progress_dir, "lit.conf", NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue