lingo-free.openapps.xyz

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.
Owned app · rev 8af89d2cc9f8
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.
← All files
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
8af89d2cc9 · 17 hours ago
Bind agent address in policy
0018350275 · 19 hours ago
Use OpenApps service URLs and add contribution strip
805365daf4 · 19 hours ago
Bounded source sync
3f2fb3b265 · 2 days ago
Bounded source sync
7dcb580324 · 2 days ago
Bounded source sync
dcd05c0944 · 2 days ago