Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 7efc97d0dde0b2a3e729ccf9
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
functions/api.ts
// Lingo's public HTTP API: no key, no account, read-only. Assistants read courses, units and lessons
// to quiz a person or explain a unit. Every route answers JSON; GET / lists the routes.
import { SITE_URL } from "./lib/config";
import { LANGUAGES, type Course, type Unit } from "./lib/course";
import es from "./courses/es.json";
import fr from "./courses/fr.json";
import de from "./courses/de.json";
import it from "./courses/it.json";
import pt from "./courses/pt.json";
const COURSES: Record<string, Course> = { es, fr, de, it, pt } as unknown as Record<string, Course>;
const json = (body: unknown, status = 200) =>
new Response(JSON.stringify(body, null, 2), {
status,
headers: { "content-type": "application/json; charset=utf-8", "access-control-allow-origin": "*", "cache-control": "public, max-age=300" },
});
const fail = (status: number, error: string) => json({ error }, status);
const lessonUrl = (lang: string, u: number, l: number) => `${SITE_URL}/learn/${lang}/lesson/${u}/${l}`;
function unitSummary(lang: string, u: Unit) {
return {
n: u.n,
title: u.title,
about: u.about,
tip: u.tip.title,
lessons: u.lessons.map((l) => ({ n: l.n, title: l.title, words: l.words.length, sentences: l.sentences.length })),
url: `${SITE_URL}/learn/${lang}`,
};
}
function unitFull(lang: string, u: Unit) {
return {
course: lang,
n: u.n,
title: u.title,
about: u.about,
tip: u.tip,
lessons: u.lessons.map((l) => ({ n: l.n, title: l.title, url: lessonUrl(lang, u.n, l.n), words: l.words, sentences: l.sentences })),
};
}
const routes = {
name: "Lingo by OpenApps",
about: "Free language courses from English (Spanish, French, German, Italian, Portuguese). Read-only, no key and no account.",
docs: `${SITE_URL}/llms.txt`,
routes: [
"GET /courses -> every course with its units and lessons",
"GET /courses/:lang -> one course: units, lesson titles and tip titles (lang: es, fr, de, it, pt)",
"GET /courses/:lang/units/:n -> a unit in full: the grammar tip, and every lesson's words and sentences",
"GET /courses/:lang/units/:n/lessons/:m -> one lesson's words and sentences",
],
fields: {
word: "{ t: target-language word (nouns with their article), e: English, ea?: other English meanings }",
sentence: "{ t: target-language sentence, e: English, ta?: other correct target-language translations, ea?: other correct English translations }",
},
};
export default async function api(req: Request) {
if (req.method !== "GET") return fail(405, "Lingo's API is read-only. Use GET.");
const parts = new URL(req.url).pathname.split("/").filter(Boolean);
if (parts[0] === "api") parts.shift();
if (!parts.length) return json(routes);
if (parts[0] !== "courses") return fail(404, "Unknown route. GET / lists the routes.");
if (parts.length === 1) {
return json({
courses: LANGUAGES.map((l) => {
const c = COURSES[l.id];
return { id: c.id, name: c.name, native: c.native, level: c.level, about: c.about, speech: c.speech, units: c.units.map((u) => unitSummary(c.id, u)) };
}),
});
}
const course = COURSES[parts[1]];
if (!course) return fail(404, `No course "${parts[1]}". Courses: ${LANGUAGES.map((l) => l.id).join(", ")}.`);
if (parts.length === 2) {
return json({ id: course.id, name: course.name, native: course.native, level: course.level, about: course.about, speech: course.speech, url: `${SITE_URL}/learn/${course.id}`, units: course.units.map((u) => unitSummary(course.id, u)) });
}
if (parts[2] !== "units" || parts.length < 4) return fail(404, "Unknown route. GET / lists the routes.");
const unit = course.units.find((u) => String(u.n) === parts[3]);
if (!unit) return fail(404, `${course.name} has units 1 to ${course.units.length}.`);
if (parts.length === 4) return json(unitFull(course.id, unit));
if (parts[4] !== "lessons" || parts.length !== 6) return fail(404, "Unknown route. GET / lists the routes.");
const lesson = unit.lessons.find((l) => String(l.n) === parts[5]);
if (!lesson) return fail(404, `Unit ${unit.n} has lessons 1 to ${unit.lessons.length}.`);
return json({
course: course.id,
unit: { n: unit.n, title: unit.title, tip: unit.tip.title },
n: lesson.n,
title: lesson.title,
url: lessonUrl(course.id, unit.n, lesson.n),
words: lesson.words,
sentences: lesson.sentences,
});
}
Change history
Point OpenApps links to openapps.free
Bind agent address in policy
Use OpenApps service URLs and add contribution strip
Bounded source sync
Bounded source sync
Bounded source sync