diff --git a/.agents/skills/livecodes/language-support/references/languages.md b/.agents/skills/livecodes/language-support/references/languages.md index f1c2ac52e..aaa73e2bb 100644 --- a/.agents/skills/livecodes/language-support/references/languages.md +++ b/.agents/skills/livecodes/language-support/references/languages.md @@ -122,6 +122,7 @@ Enabled via `processors` config: | C++ | `cpp`, | JSCPP interpreter | | C++ (Wasm) | `cpp-wasm`, `clang`, `wasm.cpp` | Clang WASM | | Crystal (Wasm) | `crystal-wasm`, `cr`, `crystal` | Crystal -> WASM (Clang/LLVM + WASI) | +| Odin (Wasm) | `odin-wasm`, `odin` | Odin (wodin, direct wasm backend + WASI) | | Objective-C | `objc-wasm`, `objc`, `wasm.m` | Clang WASM | | Objective-C++ | `objcpp-wasm`, `objcpp`, `wasm.mm` | Clang WASM | | COBOL (Wasm) | `cobol-wasm`, `cobol`, `cob`, `cbl`, `cpy`, `gnucobol`, `wasm.cobol` | GnuCOBOL (COBOL -> WASM) | diff --git a/README.md b/README.md index 14fea73e0..43da972dd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A [feature-rich](https://livecodes.io/docs/features/), open-source, **client-sid [![LiveCodes: npm version](https://img.shields.io/npm/v/livecodes)](https://www.npmjs.com/package/livecodes) [![LiveCodes: npm downloads](https://img.shields.io/npm/dm/livecodes)](https://www.npmjs.com/package/livecodes) [![LiveCodes: jsdelivr downloads](https://data.jsdelivr.com/v1/package/npm/livecodes/badge?style=rounded)](https://www.jsdelivr.com/package/npm/livecodes) -[![LiveCodes: languages](https://img.shields.io/badge/languages-124-blue)](https://livecodes.io/docs/languages/) +[![LiveCodes: languages](https://img.shields.io/badge/languages-125-blue)](https://livecodes.io/docs/languages/) [![LiveCodes: docs](https://img.shields.io/badge/Documentation-575757?logo=gitbook&logoColor=white)](https://livecodes.io/docs/) [![LiveCodes: llms.txt](https://img.shields.io/badge/llms.txt-575757?logo=googledocs&logoColor=white)](https://livecodes.io/docs/llms.txt) [![LiveCodes: llms-full.txt](https://img.shields.io/badge/llms--full.txt-575757?logo=googledocs&logoColor=white)](https://livecodes.io/docs/llms-full.txt) diff --git a/docs/docs/languages/crystal-wasm.mdx b/docs/docs/languages/crystal-wasm.mdx index 6d6f31a8b..d26e19f96 100644 --- a/docs/docs/languages/crystal-wasm.mdx +++ b/docs/docs/languages/crystal-wasm.mdx @@ -67,7 +67,7 @@ Crystal's compiler, compiled to WebAssembly. ### Version -Crystal 1.17.0, linked against LLVM 20.1.8 with the WASI sysroot from wasi-sdk 33. +Crystal v1.22.0 ## Custom Settings @@ -104,7 +104,6 @@ This comment can be added in the `hiddenContent` property of the editor for embe ## Limitations -{/* - Crystal 1.17.0 only: the wasm target does not build on newer releases. */} - Memory is never reclaimed — `wasm32` selects Crystal's no-GC allocator — so a long-running program will grow. - No threads, sockets or subprocesses: WASI preview 1 has none, and a program is given an empty filesystem. diff --git a/docs/docs/languages/odin-wasm.mdx b/docs/docs/languages/odin-wasm.mdx new file mode 100644 index 000000000..17bdc9ca4 --- /dev/null +++ b/docs/docs/languages/odin-wasm.mdx @@ -0,0 +1,253 @@ +# Odin (Wasm) + +[Odin](https://odin-lang.org/) is a general-purpose programming language with distinct typing, built for high performance, modern systems and built-in data-oriented data types. + +In LiveCodes, Odin is compiled and run entirely in the browser by Odin's compiler, compiled to WebAssembly. Nothing runs on a server. + +## Usage + +Demo: + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +export const odinConfig = { + mode: 'simple', + activeEditor: 'script', + script: { + language: 'odin-wasm', + content: `package main\n +import "core:fmt"\n +main :: proc() { + program := "+ + * - /" + accumulator := 0\n + for token in program { + switch token { + case '+': accumulator += 1 + case '-': accumulator -= 1 + case '*': accumulator *= 2 + case '/': accumulator /= 2 + case: // ignore everything else + } + }\n + fmt.printfln("The program \\"%s\\" calculates the value %d", program, accumulator) +} +`, + }, + tools: { status: 'full' }, +}; + +export const odinCanvasConfig = { + mode: 'simple', + activeEditor: 'script', + markup: { + language: 'html', + content: ` +`, + }, + script: { + language: 'odin-wasm', + content: `package main\n +import "core:fmt" +import "core:sys/wasm/js"\n +main :: proc() { + fmt.println("drawing on the canvas") +}\n +@(export) +step :: proc(dt: f64, _: rawptr) -> bool { + js.evaluate("drawDot()") + return true +} +`, + }, + customSettings: { 'odin-wasm': { target: 'js_wasm32' } }, + tools: { status: 'full' }, +}; + + + +### Communication with JavaScript + +The code is compiled in a web worker. Where it then runs depends on the target it is built for, which +is set in [custom settings](../advanced/custom-settings.mdx): + +| Target | Runs on | What it reaches | +| --- | --- | --- | +| `wasi_wasm32` (default) | a WASI host, in the worker | `core:fmt`, standard input, arguments, exit codes | +| `js_wasm32` | this page, on Odin's own JS runtime | the DOM and a ``, through `core:sys/wasm/js` | + +```json title="Custom Settings" +{ + "odin-wasm": { "target": "js_wasm32" } +} +``` + +Either way the following helpers are available in the browser global `livecodes.odinWasm` object: + +- `livecodes.odinWasm.input`: The standard input passed to the program. +- `livecodes.odinWasm.loaded`: A promise that resolves when the compiler is loaded and the program has run once. +- `livecodes.odinWasm.output`: The program's output. +- `livecodes.odinWasm.error`: The compiler diagnostics, if the code failed to compile. +- `livecodes.odinWasm.exitCode`: The program's exit code. +- `livecodes.odinWasm.run`: A function that compiles and runs the code with new input. It takes a string as input and returns a promise that resolves with an object containing `output`, `error` and `exitCode`. + +Example: + + + +#### JavaScript interop, the DOM and canvas + +With `target: "js_wasm32"` the program runs on the page, on Odin's own JavaScript runtime, so +[`core:sys/wasm/js`](https://pkg.odin-lang.org/core/sys/wasm/js/) reaches what the page has: the +document, elements, events, WebGL and a canvas. + +- `js.evaluate(str)` evaluates JavaScript in the page — the shortest path to anything the bindings do + not wrap. +- The DOM bindings: `js.get_element_value_string`, `js.set_element_value_string`, `js.set_element_style`, + `js.set_document_title`, `js.get_bounding_client_rect`. +- The event listeners in `core:sys/wasm/js/events`, and the `webgl` / `webgl2` bindings for a canvas. +- A program that exports `step :: proc(dt: f64, ctx: rawptr) -> bool` is driven once per animation + frame, as Odin's runtime does it, and the canvas is yours to draw on. Such a program still needs a + `main`: Odin requires an entry point. + +A canvas, drawn from Odin through `js.evaluate`: + + + +```odin +package main + +import "core:fmt" +import "core:sys/wasm/js" + +main :: proc() { + fmt.println("drawing on the canvas") +} + +@(export) +step :: proc(dt: f64, _: rawptr) -> bool { + js.evaluate("drawDot()") + return true +} +``` + +The page's markup provides the canvas and the `drawDot` function; the JavaScript runtime, not the +program, owns the DOM, so the two meet through `js.evaluate`. + +### Reading standard input + +The input (`livecodes.odinWasm.input`, or whatever is passed to `run`) reaches the program **byte for +byte** — nothing appends a newline to it. That matters for a line-based program, because Odin's +`bufio.reader_read_string` returns the data *together with* `.EOF` when the last line has no trailing +newline (the same as Go's `ReadString`). Read the data first, then stop on the error: + +```odin +for { + line, err := bufio.reader_read_string(&reader, '\n') + if len(line) > 0 { + // use the line + } + if err != nil { + break + } +} +``` + +## Language Info + +### Name + +`odin-wasm` + +### Aliases and Extensions + +`odin` + +### Editor + +`script` + +## Compiler + +Odin's compiler, compiled to WebAssembly by [wodin](https://github.com/karl-zylinski/wodin). + +### Version + +Odin Compiler Version: `dev-2026-09` + +## Custom Settings + +[Custom settings](../advanced/custom-settings.mdx) added to the property `odin-wasm` are passed as a JSON object to the `compiler` during compile. Please check the [documentation](../advanced/custom-settings.mdx) for full reference. + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "odin-wasm": {} +} +``` + +`args` is passed to the program as its command-line arguments, `compileArgs` is added to the `odin build` command line, and `target` selects the target the program is built for (`wasi_wasm32`, the default, which runs on a WASI host, or `js_wasm32`, which runs on the browser's JavaScript runtime). + +**Example:** + +```json title="Custom Settings" +{ + "odin-wasm": { + "args": ["--flag", "value"], + "compileArgs": ["-o:speed"] + } +} +``` + +## Live Reload + +By default, new code changes are sent to the result page for re-evaluation without a full page reload, avoiding the need to reinitialize the Odin environment. This behavior can be disabled by adding the code comment `// __livecodes_reload__` to the Odin code, which forces a full page reload. + +This comment can be added in the `hiddenContent` property of the editor for embedded playgrounds. + +## Limitations + +- The `vendor` collection is not shipped: a program that imports a `vendor:` package will not find it. +- No threads, files, sockets or subprocesses: WASI preview 1 has none, and a program is given an empty filesystem. +- A program that never returns blocks the tab; both targets run synchronously. + +## Example Usage + +```odin +package main + +import "core:fmt" + +main :: proc() { + fmt.println("hello from Odin, compiled in your browser") + + for n in 1..=5 { + fmt.printfln("%d squared is %d", n, n * n) + } +} +``` + +## Starter Template + +https://livecodes.io/?template=odin-wasm + +## Links + +- [Odin official website](https://odin-lang.org/) +- [Overview of Odin](https://odin-lang.org/docs/overview/) +- [Standard library API](https://pkg.odin-lang.org/) +- [Learn X in Y minutes, where X=Odin](https://learnxinyminutes.com/odin/) diff --git a/docs/src/components/LanguageSliders.tsx b/docs/src/components/LanguageSliders.tsx index 9c886360a..7b18df793 100644 --- a/docs/src/components/LanguageSliders.tsx +++ b/docs/src/components/LanguageSliders.tsx @@ -100,6 +100,7 @@ export default function Sliders() { { name: 'fortran', title: 'Fortran' }, { name: 'd-wasm', title: 'D (Wasm)' }, { name: 'crystal-wasm', title: 'Crystal (Wasm)' }, + { name: 'odin-wasm', title: 'Odin (Wasm)' }, { name: 'dart', title: 'Dart' }, { name: 'flutter', title: 'Flutter' }, { name: 'v-wasm', title: 'V (Wasm)' }, diff --git a/docs/src/components/TemplateList.tsx b/docs/src/components/TemplateList.tsx index 434be5678..c25520e27 100644 --- a/docs/src/components/TemplateList.tsx +++ b/docs/src/components/TemplateList.tsx @@ -56,6 +56,7 @@ const templates = [ { name: 'fortran-wasm', title: 'Fortran Starter', thumbnail: 'fortran.svg' }, { name: 'd-wasm', title: 'D Starter', thumbnail: 'd.svg' }, { name: 'crystal-wasm', title: 'Crystal Starter', thumbnail: 'crystal.svg' }, + { name: 'odin-wasm', title: 'Odin Starter', thumbnail: 'odin.svg' }, { name: 'v-wasm', title: 'V Starter', thumbnail: 'v.svg' }, { name: 'cpp-wasm', title: 'C++ (Wasm) Starter', thumbnail: 'cpp.svg' }, { name: 'objc-wasm', title: 'Objective-C (Wasm) Starter', thumbnail: 'objective-c.svg' }, diff --git a/e2e/specs/starter.spec.ts b/e2e/specs/starter.spec.ts index 9196f4eee..14069d51f 100644 --- a/e2e/specs/starter.spec.ts +++ b/e2e/specs/starter.spec.ts @@ -380,6 +380,34 @@ test.describe('Starter Templates from UI', () => { await expect(getResult().locator('text=You clicked')).toHaveText('You clicked 3 times.'); }); + test('odin-wasm Starter', async ({ page, getTestUrl, editor }) => { + // the compiler and the standard library (~6 MB) are downloaded on the first run + test.slow(); + + await page.goto(getTestUrl()); + + const { app, getResult, waitForResultUpdate } = await getLoadedApp(page); + + await app.click('[aria-label="Project"]'); + await app.click('text=New'); + await app.click('text=Odin Starter'); + + await waitForEditorFocus(app); + await waitForResultUpdate(); + + // the counter stays disabled until the toolchain has loaded and run once + await expect(getResult().locator('#counter-button')).toBeEnabled({ timeout: 180_000 }); + + await getResult().click('text=Click me'); + await getResult().click('text=Click me'); + await getResult().click('text=Click me'); + + await expect(getResult().locator('h1')).toHaveText('Hello, Odin!'); + // Each click triggers an asynchronous run, so the counter is asserted with + // retries rather than read once. + await expect(getResult().locator('text=You clicked')).toHaveText('You clicked 3 times.'); + }); + test('ada-wasm Starter', async ({ page, getTestUrl }) => { // the HAC Ada compiler (~1.4 MB) is downloaded on the first run test.slow(); diff --git a/functions/vendors/templates.js b/functions/vendors/templates.js index 523a9ba4c..0a621d42b 100644 --- a/functions/vendors/templates.js +++ b/functions/vendors/templates.js @@ -41,6 +41,7 @@ export const starterTemplates = { "nim": "Nim Starter", "nim-wasm": "Nim (Wasm) Starter", "crystal-wasm": "Crystal Starter", + "odin-wasm": "Odin Starter", "janet-wasm": "Janet (Wasm) Starter", "python": "Python Starter", "python-wasm": "Python (Wasm) Starter", diff --git a/scripts/build.js b/scripts/build.js index 5399a744b..0ed0c7cf3 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -235,6 +235,7 @@ const iifeBuild = () => 'languages/fortran-wasm/lang-fortran-wasm-script.ts', 'languages/d-wasm/lang-d-wasm-script.ts', 'languages/crystal/lang-crystal-wasm-script.ts', + 'languages/odin-wasm/lang-odin-wasm-script.ts', 'languages/cobol-wasm/lang-cobol-wasm-script.ts', 'languages/v-wasm/lang-v-wasm-script.ts', 'languages/go-wasm/lang-go-wasm-script.ts', diff --git a/server/php/inc/starter-templates.json b/server/php/inc/starter-templates.json index af0fff7c4..d1f223fbc 100644 --- a/server/php/inc/starter-templates.json +++ b/server/php/inc/starter-templates.json @@ -41,6 +41,7 @@ "nim": "Nim Starter", "nim-wasm": "Nim (Wasm) Starter", "crystal-wasm": "Crystal Starter", + "odin-wasm": "Odin Starter", "janet-wasm": "Janet (Wasm) Starter", "python": "Python Starter", "python-wasm": "Python (Wasm) Starter", diff --git a/src/livecodes/UI/command-menu-actions.ts b/src/livecodes/UI/command-menu-actions.ts index 64d9c1556..b4b99e5a4 100644 --- a/src/livecodes/UI/command-menu-actions.ts +++ b/src/livecodes/UI/command-menu-actions.ts @@ -314,6 +314,7 @@ export const getCommandMenuActions = ({ 'fortran-wasm', 'd-wasm', 'crystal-wasm', + 'odin-wasm', 'v-wasm', 'rust-wasm', 'swift-wasm', diff --git a/src/livecodes/assets/templates/odin.svg b/src/livecodes/assets/templates/odin.svg new file mode 100644 index 000000000..c4ae2e288 --- /dev/null +++ b/src/livecodes/assets/templates/odin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index 09f7d7a69..6bf34b882 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -1890,6 +1890,40 @@

OCaml

+
+

Odin (Wasm)

+
+ Odin's own compiler, compiled to WebAssembly, running entirely in the browser. +
+ +

Pascal

diff --git a/src/livecodes/i18n/locales/ar/language-info.lokalise.json b/src/livecodes/i18n/locales/ar/language-info.lokalise.json index 54be4eed4..26d2173ef 100644 --- a/src/livecodes/i18n/locales/ar/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ar/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "مترجم Odin الخاص، المُجمَّع إلى WebAssembly، يعمل بالكامل داخل المتصفح." + }, + "odin-wasm.link": { + "translation": " الموقع الرسمي لـ Odin نظرة عامة على Odin واجهة برمجة المكتبة القياسية تعلم X في Y دقيقة، حيث X = Odin وثائق LiveCodes تحميل قالب البدء " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal يعمل في المتصفح باستخدام pas2js (من فريق Free Pascal) ومُجمَّع إلى WebAssembly." }, diff --git a/src/livecodes/i18n/locales/ar/language-info.ts b/src/livecodes/i18n/locales/ar/language-info.ts index 902b978a4..7ef304336 100644 --- a/src/livecodes/i18n/locales/ar/language-info.ts +++ b/src/livecodes/i18n/locales/ar/language-info.ts @@ -358,6 +358,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>موقع OCaml <3> <4>وثائق OCaml <5> <6>تعلم X في Y دقائق، حيث X=OCaml <7> <8>تحميل القالب الأولي ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'مترجم Odin الخاص، المُجمَّع إلى WebAssembly، يعمل بالكامل داخل المتصفح.', + link: '<1> <2>الموقع الرسمي لـ Odin <3> <4>نظرة عامة على Odin <5> <6>واجهة برمجة المكتبة القياسية <7> <8>تعلم X في Y دقيقة، حيث X = Odin <9> <10>وثائق LiveCodes <11> <12>تحميل قالب البدء ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal يعمل في المتصفح باستخدام pas2js (من فريق Free Pascal) ومُجمَّع إلى WebAssembly.', link: '<1> <2>الموقع الرسمي لـ Free Pascal <3> <4>وثائق pas2js <5> <6>pas2js wiki <7> <8>وثائق LiveCodes <9> <10>تحميل القالب الأولي ', diff --git a/src/livecodes/i18n/locales/ar/translation.lokalise.json b/src/livecodes/i18n/locales/ar/translation.lokalise.json index bce161586..f18218349 100644 --- a/src/livecodes/i18n/locales/ar/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ar/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "قالب OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "قالب Odin" + }, "templates.starter.pascal": { "translation": "قالب Pascal" }, diff --git a/src/livecodes/i18n/locales/ar/translation.ts b/src/livecodes/i18n/locales/ar/translation.ts index b69c1e35e..7ff51c18b 100644 --- a/src/livecodes/i18n/locales/ar/translation.ts +++ b/src/livecodes/i18n/locales/ar/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'قالب Objective-C (Wasm)', ocaml: 'قالب Ocaml', 'ocaml-js': 'قالب OCaml (JS)', + 'odin-wasm': 'قالب Odin', pascal: 'قالب Pascal', perl: 'قالب Perl', phaser: 'قالب Phaser', diff --git a/src/livecodes/i18n/locales/bn/language-info.lokalise.json b/src/livecodes/i18n/locales/bn/language-info.lokalise.json index d2992dd1b..2e2cecc15 100644 --- a/src/livecodes/i18n/locales/bn/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/bn/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin-এর নিজস্ব কম্পাইলার, WebAssembly-তে কম্পাইল করা, সম্পূর্ণভাবে ব্রাউজারে চলে।" + }, + "odin-wasm.link": { + "translation": " Odin অফিসিয়াল ওয়েবসাইট Odin-এর সংক্ষিপ্ত বিবরণ স্ট্যান্ডার্ড লাইব্রেরি API Y মিনিটে X শিখুন, যেখানে X=Odin লাইভকোড ডকুমেন্টেশন স্টার্টার টেমপ্লেট লোড করুন " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Free Pascal দলের pas2js ব্যবহার করে ব্রাউজারে Pascal চলছে, যা WebAssembly-তে কম্পাইল করা।" }, diff --git a/src/livecodes/i18n/locales/bn/language-info.ts b/src/livecodes/i18n/locales/bn/language-info.ts index c286897d4..eafd2ec09 100644 --- a/src/livecodes/i18n/locales/bn/language-info.ts +++ b/src/livecodes/i18n/locales/bn/language-info.ts @@ -362,6 +362,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml ওয়েবসাইট <3> <4>OCaml ডকুমেন্টেশন <5> <6>Y মিনিটে X শিখুন, যেখানে X=OCaml <7> <8>স্টার্টার টেমপ্লেট লোড করুন ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odin-এর নিজস্ব কম্পাইলার, WebAssembly-তে কম্পাইল করা, সম্পূর্ণভাবে ব্রাউজারে চলে।', + link: '<1> <2>Odin অফিসিয়াল ওয়েবসাইট <3> <4>Odin-এর সংক্ষিপ্ত বিবরণ <5> <6>স্ট্যান্ডার্ড লাইব্রেরি API <7> <8>Y মিনিটে X শিখুন, যেখানে X=Odin <9> <10>লাইভকোড ডকুমেন্টেশন <11> <12>স্টার্টার টেমপ্লেট লোড করুন ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Free Pascal দলের pas2js ব্যবহার করে ব্রাউজারে Pascal চলছে, যা WebAssembly-তে কম্পাইল করা।', link: '<1> <2>Free Pascal অফিসিয়াল ওয়েবসাইট <3> <4>pas2js ডকুমেন্টেশন <5> <6>pas2js wiki <7> <8>LiveCodes ডকুমেন্টেশন <9> <10>স্টার্টার টেমপ্লেট লোড করুন ', diff --git a/src/livecodes/i18n/locales/bn/translation.lokalise.json b/src/livecodes/i18n/locales/bn/translation.lokalise.json index 35cb6a659..304fe216c 100644 --- a/src/livecodes/i18n/locales/bn/translation.lokalise.json +++ b/src/livecodes/i18n/locales/bn/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS) স্টার্টার" }, + "templates.starter.odin-wasm": { + "translation": "Odin স্টার্টার" + }, "templates.starter.pascal": { "translation": "Pascal স্টার্টার" }, diff --git a/src/livecodes/i18n/locales/bn/translation.ts b/src/livecodes/i18n/locales/bn/translation.ts index 261aff8d1..330bd4dd8 100644 --- a/src/livecodes/i18n/locales/bn/translation.ts +++ b/src/livecodes/i18n/locales/bn/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm) স্টার্টার', ocaml: 'Ocaml স্টার্টার', 'ocaml-js': 'OCaml (JS) স্টার্টার', + 'odin-wasm': 'Odin স্টার্টার', pascal: 'Pascal স্টার্টার', perl: 'Perl স্টার্টার', phaser: 'Phaser স্টার্টার', diff --git a/src/livecodes/i18n/locales/de/language-info.lokalise.json b/src/livecodes/i18n/locales/de/language-info.lokalise.json index 07ea64207..8552ed6f9 100644 --- a/src/livecodes/i18n/locales/de/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/de/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odins eigener Compiler, zu WebAssembly kompiliert, läuft vollständig im Browser." + }, + "odin-wasm.link": { + "translation": " Offizielle Website von Odin Überblick über Odin Standardbibliothek-API Lerne X in Y Minuten, wobei X = Odin LiveCodes-Dokumentation Startervorlage laden " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal läuft im Browser mit pas2js (vom Free Pascal-Team) und wird zu WebAssembly kompiliert." }, diff --git a/src/livecodes/i18n/locales/de/language-info.ts b/src/livecodes/i18n/locales/de/language-info.ts index 4eadb5a4f..21621428c 100644 --- a/src/livecodes/i18n/locales/de/language-info.ts +++ b/src/livecodes/i18n/locales/de/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml Website <3> <4>OCaml Dokumentation <5> <6>Lerne X in Y Minuten, wobei X=OCaml <7> <8>Starter-Vorlage laden ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odins eigener Compiler, zu WebAssembly kompiliert, läuft vollständig im Browser.', + link: '<1> <2>Offizielle Website von Odin <3> <4>Überblick über Odin <5> <6>Standardbibliothek-API <7> <8>Lerne X in Y Minuten, wobei X = Odin <9> <10>LiveCodes-Dokumentation <11> <12>Startervorlage laden ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal läuft im Browser mit pas2js (vom Free Pascal-Team) und wird zu WebAssembly kompiliert.', link: '<1> <2>Offizielle Free Pascal-Website <3> <4>pas2js-Dokumentation <5> <6>pas2js wiki <7> <8>LiveCodes-Dokumentationen <9> <10>Starter-Vorlage laden ', diff --git a/src/livecodes/i18n/locales/de/translation.lokalise.json b/src/livecodes/i18n/locales/de/translation.lokalise.json index 275aad54b..f8162ebd1 100644 --- a/src/livecodes/i18n/locales/de/translation.lokalise.json +++ b/src/livecodes/i18n/locales/de/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS)-Starter" }, + "templates.starter.odin-wasm": { + "translation": "Odin-Starter" + }, "templates.starter.pascal": { "translation": "Pascal-Starter" }, diff --git a/src/livecodes/i18n/locales/de/translation.ts b/src/livecodes/i18n/locales/de/translation.ts index a53e89338..c1543f390 100644 --- a/src/livecodes/i18n/locales/de/translation.ts +++ b/src/livecodes/i18n/locales/de/translation.ts @@ -1040,6 +1040,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm)-Starter', ocaml: 'OCaml-Starter', 'ocaml-js': 'OCaml (JS)-Starter', + 'odin-wasm': 'Odin-Starter', pascal: 'Pascal-Starter', perl: 'Perl-Starter', phaser: 'Phaser-Starter', diff --git a/src/livecodes/i18n/locales/en/language-info.lokalise.json b/src/livecodes/i18n/locales/en/language-info.lokalise.json index 9bdead6b5..90ca83dc2 100644 --- a/src/livecodes/i18n/locales/en/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/en/language-info.lokalise.json @@ -864,6 +864,18 @@ "notes": "", "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "notes": "", + "translation": "Odin's own compiler, compiled to WebAssembly, running entirely in the browser." + }, + "odin-wasm.link": { + "notes": "### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n", + "translation": " Odin official website Overview of Odin Standard library API Learn X in Y minutes, where X=Odin LiveCodes Documentation Load starter template " + }, + "odin-wasm.name": { + "notes": "", + "translation": "Odin (Wasm)" + }, "pascal.desc": { "notes": "", "translation": "Pascal running in the browser using pas2js (by the Free Pascal team) compiled to WebAssembly." diff --git a/src/livecodes/i18n/locales/en/language-info.ts b/src/livecodes/i18n/locales/en/language-info.ts index fb94cc563..97da69a43 100644 --- a/src/livecodes/i18n/locales/en/language-info.ts +++ b/src/livecodes/i18n/locales/en/language-info.ts @@ -375,6 +375,11 @@ const languageInfo = { link: '<1><2>OCaml website <3> <4>OCaml documentation <5> <6>Learn X in Y minutes, where X=OCaml <7> <8>Load starter template ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: "Odin's own compiler, compiled to WebAssembly, running entirely in the browser.", + link: '<1> <2>Odin official website <3> <4>Overview of Odin <5> <6>Standard library API <7> <8>Learn X in Y minutes, where X=Odin <9> <10>LiveCodes Documentation <11> <12>Load starter template ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal running in the browser using pas2js (by the Free Pascal team) compiled to WebAssembly.', link: '<1> <2>Free Pascal website <3> <4>pas2js documentation <5> <6>pas2js wiki <7> <8>LiveCodes Documentation <9> <10>Load starter template ', diff --git a/src/livecodes/i18n/locales/en/translation.lokalise.json b/src/livecodes/i18n/locales/en/translation.lokalise.json index 96ea43998..6ea7baf23 100644 --- a/src/livecodes/i18n/locales/en/translation.lokalise.json +++ b/src/livecodes/i18n/locales/en/translation.lokalise.json @@ -2800,6 +2800,10 @@ "notes": "", "translation": "OCaml (JS) Starter" }, + "templates.starter.odin-wasm": { + "notes": "", + "translation": "Odin Starter" + }, "templates.starter.pascal": { "notes": "", "translation": "Pascal Starter" diff --git a/src/livecodes/i18n/locales/en/translation.ts b/src/livecodes/i18n/locales/en/translation.ts index e3940de1b..adc249477 100644 --- a/src/livecodes/i18n/locales/en/translation.ts +++ b/src/livecodes/i18n/locales/en/translation.ts @@ -1046,6 +1046,7 @@ const translation = { 'objc-wasm': 'Objective-C (Wasm) Starter', ocaml: 'Ocaml Starter', 'ocaml-js': 'OCaml (JS) Starter', + 'odin-wasm': 'Odin Starter', pascal: 'Pascal Starter', perl: 'Perl Starter', phaser: 'Phaser Starter', diff --git a/src/livecodes/i18n/locales/es/language-info.lokalise.json b/src/livecodes/i18n/locales/es/language-info.lokalise.json index 519fbe2df..ad6b6a9fb 100644 --- a/src/livecodes/i18n/locales/es/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/es/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "El propio compilador de Odin, compilado a WebAssembly, ejecutándose íntegramente en el navegador." + }, + "odin-wasm.link": { + "translation": " Página web oficial de Odin Descripción general de Odin API de la biblioteca estándar Aprende X en Y minutos, donde X = Odin Documentación de LiveCodes Cargar plantilla de inicio " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal ejecutándose en el navegador usando pas2js (por el equipo de Free Pascal), compilado a WebAssembly." }, diff --git a/src/livecodes/i18n/locales/es/language-info.ts b/src/livecodes/i18n/locales/es/language-info.ts index 0deceb75a..8cb9d35bf 100644 --- a/src/livecodes/i18n/locales/es/language-info.ts +++ b/src/livecodes/i18n/locales/es/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Sitio web de OCaml <3> <4>Documentación de OCaml <5> <6>Aprende X en Y minutos, donde X=OCaml <7> <8>Cargar plantilla inicial ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'El propio compilador de Odin, compilado a WebAssembly, ejecutándose íntegramente en el navegador.', + link: '<1> <2>Página web oficial de Odin <3> <4>Descripción general de Odin <5> <6>API de la biblioteca estándar <7> <8>Aprende X en Y minutos, donde X = Odin <9> <10>Documentación de LiveCodes <11> <12>Cargar plantilla de inicio ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal ejecutándose en el navegador usando pas2js (por el equipo de Free Pascal), compilado a WebAssembly.', link: '<1> <2>Sitio web oficial de Free Pascal <3> <4>Documentación de pas2js <5> <6>pas2js wiki <7> <8>Documentación de LiveCodes <9> <10>Cargar plantilla inicial ', diff --git a/src/livecodes/i18n/locales/es/translation.lokalise.json b/src/livecodes/i18n/locales/es/translation.lokalise.json index d283f3819..33bf4cf86 100644 --- a/src/livecodes/i18n/locales/es/translation.lokalise.json +++ b/src/livecodes/i18n/locales/es/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Inicio OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Inicio Odin" + }, "templates.starter.pascal": { "translation": "Inicio Pascal" }, diff --git a/src/livecodes/i18n/locales/es/translation.ts b/src/livecodes/i18n/locales/es/translation.ts index c5f3a96fd..a53f3e6c0 100644 --- a/src/livecodes/i18n/locales/es/translation.ts +++ b/src/livecodes/i18n/locales/es/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Inicio Objective-C (Wasm)', ocaml: 'Inicio Ocaml', 'ocaml-js': 'Inicio OCaml (JS)', + 'odin-wasm': 'Inicio Odin', pascal: 'Inicio Pascal', perl: 'Inicio Perl', phaser: 'Inicio Phaser', diff --git a/src/livecodes/i18n/locales/fa/language-info.lokalise.json b/src/livecodes/i18n/locales/fa/language-info.lokalise.json index b38d70c44..698420cb6 100644 --- a/src/livecodes/i18n/locales/fa/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/fa/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "کامپایلر اختصاصی Odin، که به WebAssembly کامپایل شده، کاملاً در مرورگر اجرا می‌شود." + }, + "odin-wasm.link": { + "translation": " وب‌سایت رسمی Odin مروری بر Odin API کتابخانه استاندارد یادگیری X در Y دقیقه، که X=Odin مستندات LiveCodes بارگیری قالب شروع " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal با استفاده از pas2js (توسط تیم Free Pascal) در مرورگر اجرا می‌شود و به WebAssembly کامپایل شده است." }, diff --git a/src/livecodes/i18n/locales/fa/language-info.ts b/src/livecodes/i18n/locales/fa/language-info.ts index fd34c3514..96cc510d0 100644 --- a/src/livecodes/i18n/locales/fa/language-info.ts +++ b/src/livecodes/i18n/locales/fa/language-info.ts @@ -363,6 +363,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>وب‌سایت OCaml <3> <4>مستندات OCaml <5> <6>یادگیری X در Y دقیقه، جایی که X=OCaml <7> <8>بارگذاری قالب شروع کننده ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'کامپایلر اختصاصی Odin، که به WebAssembly کامپایل شده، کاملاً در مرورگر اجرا می‌شود.', + link: '<1> <2>وب‌سایت رسمی Odin <3> <4>مروری بر Odin <5> <6>API کتابخانه استاندارد <7> <8>یادگیری X در Y دقیقه، که X=Odin <9> <10>مستندات LiveCodes <11> <12>بارگیری قالب شروع ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal با استفاده از pas2js (توسط تیم Free Pascal) در مرورگر اجرا می‌شود و به WebAssembly کامپایل شده است.', link: '<1> <2>وب‌سایت رسمی Free Pascal <3> <4>مستندات pas2js <5> <6>pas2js wiki <7> <8>مستندات LiveCodes <9> <10>بارگذاری قالب شروع کننده ', diff --git a/src/livecodes/i18n/locales/fa/translation.lokalise.json b/src/livecodes/i18n/locales/fa/translation.lokalise.json index 35d3b127f..d526a7428 100644 --- a/src/livecodes/i18n/locales/fa/translation.lokalise.json +++ b/src/livecodes/i18n/locales/fa/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "شروع کننده OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "شروع کننده Odin" + }, "templates.starter.pascal": { "translation": "شروع کننده Pascal" }, diff --git a/src/livecodes/i18n/locales/fa/translation.ts b/src/livecodes/i18n/locales/fa/translation.ts index 51b6c0b6f..08c0d6fc4 100644 --- a/src/livecodes/i18n/locales/fa/translation.ts +++ b/src/livecodes/i18n/locales/fa/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'شروع کننده Objective-C (Wasm)', ocaml: 'شروع کننده Ocaml', 'ocaml-js': 'شروع کننده OCaml (JS)', + 'odin-wasm': 'شروع کننده Odin', pascal: 'شروع کننده Pascal', perl: 'شروع کننده Perl', phaser: 'شروع کننده Phaser', diff --git a/src/livecodes/i18n/locales/fr/language-info.lokalise.json b/src/livecodes/i18n/locales/fr/language-info.lokalise.json index 8e19867a1..c3407ec73 100644 --- a/src/livecodes/i18n/locales/fr/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/fr/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Le compilateur propre à Odin, compilé en WebAssembly, s'exécutant entièrement dans le navigateur." + }, + "odin-wasm.link": { + "translation": " Site officiel d'Odin Présentation d'Odin API de la bibliothèque standard Apprenez X en Y minutes, où X = Odin Documentation LiveCodes Charger un modèle de démarrage " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal s'exécutant dans le navigateur en utilisant pas2js (par l'équipe Free Pascal), compilé en WebAssembly." }, diff --git a/src/livecodes/i18n/locales/fr/language-info.ts b/src/livecodes/i18n/locales/fr/language-info.ts index 305e41403..0188e651e 100644 --- a/src/livecodes/i18n/locales/fr/language-info.ts +++ b/src/livecodes/i18n/locales/fr/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Site web OCaml <3> <4>Documentation OCaml <5> <6>Apprenez X en Y minutes, où X=OCaml <7> <8>Charger le modèle de démarrage ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: "Le compilateur propre à Odin, compilé en WebAssembly, s'exécutant entièrement dans le navigateur.", + link: "<1> <2>Site officiel d'Odin <3> <4>Présentation d'Odin <5> <6>API de la bibliothèque standard <7> <8>Apprenez X en Y minutes, où X = Odin <9> <10>Documentation LiveCodes <11> <12>Charger un modèle de démarrage ", + name: 'Odin (Wasm)', + }, pascal: { desc: "Pascal s'exécutant dans le navigateur en utilisant pas2js (par l'équipe Free Pascal), compilé en WebAssembly.", link: '<1> <2>Site officiel de Free Pascal <3> <4>Documentation pas2js <5> <6>pas2js wiki <7> <8>Documentations LiveCodes <9> <10>Charger le modèle de démarrage ', diff --git a/src/livecodes/i18n/locales/fr/translation.lokalise.json b/src/livecodes/i18n/locales/fr/translation.lokalise.json index 8d389cfed..0de06d0a4 100644 --- a/src/livecodes/i18n/locales/fr/translation.lokalise.json +++ b/src/livecodes/i18n/locales/fr/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Démarrage OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Démarrage Odin" + }, "templates.starter.pascal": { "translation": "Démarrage Pascal" }, diff --git a/src/livecodes/i18n/locales/fr/translation.ts b/src/livecodes/i18n/locales/fr/translation.ts index 5dd8217e0..8bdb62ac8 100644 --- a/src/livecodes/i18n/locales/fr/translation.ts +++ b/src/livecodes/i18n/locales/fr/translation.ts @@ -1040,6 +1040,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Démarrage Objective-C (Wasm)', ocaml: 'Démarrage OCaml', 'ocaml-js': 'Démarrage OCaml (JS)', + 'odin-wasm': 'Démarrage Odin', pascal: 'Démarrage Pascal', perl: 'Démarrage Perl', phaser: 'Démarrage Phaser', diff --git a/src/livecodes/i18n/locales/hi/language-info.lokalise.json b/src/livecodes/i18n/locales/hi/language-info.lokalise.json index 659b0f087..df92603af 100644 --- a/src/livecodes/i18n/locales/hi/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/hi/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin का अपना कंपाइलर, WebAssembly में संकलित, पूरी तरह से ब्राउज़र में चलता है।" + }, + "odin-wasm.link": { + "translation": " Odin आधिकारिक वेबसाइट Odin का अवलोकन मानक पुस्तकालय API Y मिनट में X सीखें, जहाँ X=Odin लाइवकोड दस्तावेजीकरण स्टार्टर टेम्पलेट लोड करें " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Free Pascal टीम द्वारा pas2js का उपयोग करके ब्राउज़र में चलने वाला Pascal, जो WebAssembly में संकलित है।" }, diff --git a/src/livecodes/i18n/locales/hi/language-info.ts b/src/livecodes/i18n/locales/hi/language-info.ts index b86018be3..ea4a44ebd 100644 --- a/src/livecodes/i18n/locales/hi/language-info.ts +++ b/src/livecodes/i18n/locales/hi/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml वेबसाइट <3> <4>OCaml दस्तावेज़ीकरण <5> <6>X को Y मिनटों में सीखें, जहां X=OCaml <7> <8>स्टार्टर टेम्पलेट लोड करें ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odin का अपना कंपाइलर, WebAssembly में संकलित, पूरी तरह से ब्राउज़र में चलता है।', + link: '<1> <2>Odin आधिकारिक वेबसाइट <3> <4>Odin का अवलोकन <5> <6>मानक पुस्तकालय API <7> <8>Y मिनट में X सीखें, जहाँ X=Odin <9> <10>लाइवकोड दस्तावेजीकरण <11> <12>स्टार्टर टेम्पलेट लोड करें ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Free Pascal टीम द्वारा pas2js का उपयोग करके ब्राउज़र में चलने वाला Pascal, जो WebAssembly में संकलित है।', link: '<1> <2>Free Pascal आधिकारिक वेबसाइट <3> <4>pas2js दस्तावेज़ीकरण <5> <6>pas2js wiki <7> <8>लाइवकोड्स (LiveCodes) दस्तावेज़ <9> <10>स्टार्टर टेम्पलेट लोड करें ', diff --git a/src/livecodes/i18n/locales/hi/translation.lokalise.json b/src/livecodes/i18n/locales/hi/translation.lokalise.json index 8f97d032b..42dcf5ecf 100644 --- a/src/livecodes/i18n/locales/hi/translation.lokalise.json +++ b/src/livecodes/i18n/locales/hi/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS) स्टार्टर" }, + "templates.starter.odin-wasm": { + "translation": "Odin स्टार्टर" + }, "templates.starter.pascal": { "translation": "Pascal स्टार्टर" }, diff --git a/src/livecodes/i18n/locales/hi/translation.ts b/src/livecodes/i18n/locales/hi/translation.ts index 9c4acc8b7..187c47afb 100644 --- a/src/livecodes/i18n/locales/hi/translation.ts +++ b/src/livecodes/i18n/locales/hi/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm) स्टार्टर', ocaml: 'OCaml स्टार्टर', 'ocaml-js': 'OCaml (JS) स्टार्टर', + 'odin-wasm': 'Odin स्टार्टर', pascal: 'Pascal स्टार्टर', perl: 'Perl स्टार्टर', phaser: 'Phaser स्टार्टर', diff --git a/src/livecodes/i18n/locales/hu/language-info.lokalise.json b/src/livecodes/i18n/locales/hu/language-info.lokalise.json index eb61d66ee..638fb71df 100644 --- a/src/livecodes/i18n/locales/hu/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/hu/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Az Odin saját fordítója WebAssembly-re fordítva, teljes egészében a böngészőben fut." + }, + "odin-wasm.link": { + "translation": " Az Odin hivatalos weboldala Az Odin áttekintése Standard könyvtár API Tanulj meg X-et Y perc alatt, ahol X=Odin LiveCodes dokumentáció Kezdő sablon betöltése " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "A Pascal a pas2js (a Free Pascal csapatától) segítségével fut a böngészőben, WebAssembly-re fordítva." }, diff --git a/src/livecodes/i18n/locales/hu/language-info.ts b/src/livecodes/i18n/locales/hu/language-info.ts index bb4cedb9a..2cae07146 100644 --- a/src/livecodes/i18n/locales/hu/language-info.ts +++ b/src/livecodes/i18n/locales/hu/language-info.ts @@ -363,6 +363,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml weboldal <3> <4>OCaml dokumentáció <5> <6>Tanulj X-et Y perc alatt, ahol X=OCaml <7> <8>Kezdő sablon betöltése ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Az Odin saját fordítója WebAssembly-re fordítva, teljes egészében a böngészőben fut.', + link: '<1> <2>Az Odin hivatalos weboldala <3> <4>Az Odin áttekintése <5> <6>Standard könyvtár API <7> <8>Tanulj meg X-et Y perc alatt, ahol X=Odin <9> <10>LiveCodes dokumentáció <11> <12>Kezdő sablon betöltése ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'A Pascal a pas2js (a Free Pascal csapatától) segítségével fut a böngészőben, WebAssembly-re fordítva.', link: '<1> <2>Free Pascal hivatalos weboldal <3> <4>pas2js dokumentáció <5> <6>pas2js wiki <7> <8>LiveCodes dokumentációk <9> <10>Kezdő sablon betöltése ', diff --git a/src/livecodes/i18n/locales/hu/translation.lokalise.json b/src/livecodes/i18n/locales/hu/translation.lokalise.json index 5635b2b28..ef317333c 100644 --- a/src/livecodes/i18n/locales/hu/translation.lokalise.json +++ b/src/livecodes/i18n/locales/hu/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS) kezdő" }, + "templates.starter.odin-wasm": { + "translation": "Odin kezdő" + }, "templates.starter.pascal": { "translation": "Pascal kezdő" }, diff --git a/src/livecodes/i18n/locales/hu/translation.ts b/src/livecodes/i18n/locales/hu/translation.ts index 3a0efce1a..7a4fe5e11 100644 --- a/src/livecodes/i18n/locales/hu/translation.ts +++ b/src/livecodes/i18n/locales/hu/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm) kezdő', ocaml: 'OCaml kezdő', 'ocaml-js': 'OCaml (JS) kezdő', + 'odin-wasm': 'Odin kezdő', pascal: 'Pascal kezdő', perl: 'Perl kezdő', phaser: 'Phaser kezdő', diff --git a/src/livecodes/i18n/locales/id/language-info.lokalise.json b/src/livecodes/i18n/locales/id/language-info.lokalise.json index 6b2534e82..d4e26d041 100644 --- a/src/livecodes/i18n/locales/id/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/id/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Kompiler milik Odin sendiri, dikompilasi ke WebAssembly, berjalan sepenuhnya di dalam browser." + }, + "odin-wasm.link": { + "translation": " Situs web resmi Odin Ikhtisar Odin API pustaka standar Belajar X dalam Y menit, di mana X = Odin Dokumentasi LiveCodes Muat templat awal " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal berjalan di browser menggunakan pas2js (oleh tim Free Pascal), dikompilasi ke WebAssembly." }, diff --git a/src/livecodes/i18n/locales/id/language-info.ts b/src/livecodes/i18n/locales/id/language-info.ts index d5501b3d8..71e6c6a45 100644 --- a/src/livecodes/i18n/locales/id/language-info.ts +++ b/src/livecodes/i18n/locales/id/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Situs web OCaml <3> <4>Dokumentasi OCaml <5> <6>Pelajari X dalam Y menit, dengan X=OCaml <7> <8>Muat template pemula ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Kompiler milik Odin sendiri, dikompilasi ke WebAssembly, berjalan sepenuhnya di dalam browser.', + link: '<1> <2>Situs web resmi Odin <3> <4>Ikhtisar Odin <5> <6>API pustaka standar <7> <8>Belajar X dalam Y menit, di mana X = Odin <9> <10>Dokumentasi LiveCodes <11> <12>Muat templat awal ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal berjalan di browser menggunakan pas2js (oleh tim Free Pascal), dikompilasi ke WebAssembly.', link: '<1> <2>Situs web resmi Free Pascal <3> <4>Dokumentasi pas2js <5> <6>pas2js wiki <7> <8>Dokumentasi LiveCodes <9> <10>Muat template pemula ', diff --git a/src/livecodes/i18n/locales/id/translation.lokalise.json b/src/livecodes/i18n/locales/id/translation.lokalise.json index 8bd8ef106..e9b4412f5 100644 --- a/src/livecodes/i18n/locales/id/translation.lokalise.json +++ b/src/livecodes/i18n/locales/id/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Pemula OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Pemula Odin" + }, "templates.starter.pascal": { "translation": "Pemula Pascal" }, diff --git a/src/livecodes/i18n/locales/id/translation.ts b/src/livecodes/i18n/locales/id/translation.ts index ceca4fe3e..389c4a34b 100644 --- a/src/livecodes/i18n/locales/id/translation.ts +++ b/src/livecodes/i18n/locales/id/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Pemula Objective-C (Wasm)', ocaml: 'Pemula Ocaml', 'ocaml-js': 'Pemula OCaml (JS)', + 'odin-wasm': 'Pemula Odin', pascal: 'Pemula Pascal', perl: 'Pemula Perl', phaser: 'Pemula Phaser', diff --git a/src/livecodes/i18n/locales/it/language-info.lokalise.json b/src/livecodes/i18n/locales/it/language-info.lokalise.json index 2fabcfaf9..b3261bb67 100644 --- a/src/livecodes/i18n/locales/it/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/it/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Il compilatore proprio di Odin, compilato in WebAssembly, eseguito interamente nel browser." + }, + "odin-wasm.link": { + "translation": " Sito web ufficiale di Odin Panoramica di Odin API della libreria standard Impara X in Y minuti, dove X = Odin Documentazione di LiveCodes Carica il modello di avvio " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal in esecuzione nel browser utilizzando pas2js (del team Free Pascal), compilato in WebAssembly." }, diff --git a/src/livecodes/i18n/locales/it/language-info.ts b/src/livecodes/i18n/locales/it/language-info.ts index b451da494..e7b6cfe1c 100644 --- a/src/livecodes/i18n/locales/it/language-info.ts +++ b/src/livecodes/i18n/locales/it/language-info.ts @@ -363,6 +363,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Sito web di OCaml <3> <4>Documentazione di OCaml <5> <6>Impara X in Y minuti, dove X=OCaml <7> <8>Carica modello di partenza ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Il compilatore proprio di Odin, compilato in WebAssembly, eseguito interamente nel browser.', + link: '<1> <2>Sito web ufficiale di Odin <3> <4>Panoramica di Odin <5> <6>API della libreria standard <7> <8>Impara X in Y minuti, dove X = Odin <9> <10>Documentazione di LiveCodes <11> <12>Carica il modello di avvio ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal in esecuzione nel browser utilizzando pas2js (del team Free Pascal), compilato in WebAssembly.', link: '<1> <2>Sito ufficiale di Free Pascal <3> <4>Documentazione di pas2js <5> <6>pas2js wiki <7> <8>Documentazione LiveCodes <9> <10>Carica modello di partenza ', diff --git a/src/livecodes/i18n/locales/it/translation.lokalise.json b/src/livecodes/i18n/locales/it/translation.lokalise.json index a63c0094b..55c046d46 100644 --- a/src/livecodes/i18n/locales/it/translation.lokalise.json +++ b/src/livecodes/i18n/locales/it/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Starter OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Starter Odin" + }, "templates.starter.pascal": { "translation": "Starter Pascal" }, diff --git a/src/livecodes/i18n/locales/it/translation.ts b/src/livecodes/i18n/locales/it/translation.ts index cd499d518..ab3ff2108 100644 --- a/src/livecodes/i18n/locales/it/translation.ts +++ b/src/livecodes/i18n/locales/it/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Starter Objective-C (Wasm)', ocaml: 'Starter OCaml', 'ocaml-js': 'Starter OCaml (JS)', + 'odin-wasm': 'Starter Odin', pascal: 'Starter Pascal', perl: 'Starter Perl', phaser: 'Starter Phaser', diff --git a/src/livecodes/i18n/locales/ja/language-info.lokalise.json b/src/livecodes/i18n/locales/ja/language-info.lokalise.json index 1290fff80..d019b7d5a 100644 --- a/src/livecodes/i18n/locales/ja/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ja/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin 自身のコンパイラを WebAssembly にコンパイルしたもので、ブラウザ内で完全に実行されます。" + }, + "odin-wasm.link": { + "translation": " Odin 公式サイト Odin の概要 標準ライブラリ API 「Y分でXを学ぶ」(X=Odin) LiveCodes ドキュメント スターターテンプレートの読み込み " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "pas2js(Free Pascal チームによる)を使用してブラウザで動作する Pascal。WebAssembly にコンパイルされます。" }, diff --git a/src/livecodes/i18n/locales/ja/language-info.ts b/src/livecodes/i18n/locales/ja/language-info.ts index d7162414a..6898af047 100644 --- a/src/livecodes/i18n/locales/ja/language-info.ts +++ b/src/livecodes/i18n/locales/ja/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml ウェブサイト <3> <4>OCaml ドキュメント <5> <6>X分でYを学ぶ、X=OCaml <7> <8>スターターテンプレートを読み込む ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odin 自身のコンパイラを WebAssembly にコンパイルしたもので、ブラウザ内で完全に実行されます。', + link: '<1> <2>Odin 公式サイト <3> <4>Odin の概要 <5> <6>標準ライブラリ API <7> <8>「Y分でXを学ぶ」(X=Odin) <9> <10>LiveCodes ドキュメント <11> <12>スターターテンプレートの読み込み ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'pas2js(Free Pascal チームによる)を使用してブラウザで動作する Pascal。WebAssembly にコンパイルされます。', link: '<1> <2>Free Pascal 公式ウェブサイト <3> <4>pas2js ドキュメント <5> <6>pas2js wiki <7> <8>LiveCodes ドキュメント <9> <10>スターターテンプレートを読み込む ', diff --git a/src/livecodes/i18n/locales/ja/translation.lokalise.json b/src/livecodes/i18n/locales/ja/translation.lokalise.json index 02cab7c9d..43d30d259 100644 --- a/src/livecodes/i18n/locales/ja/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ja/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml(JS)スターター" }, + "templates.starter.odin-wasm": { + "translation": "Odin スターター" + }, "templates.starter.pascal": { "translation": "Pascalスターター" }, diff --git a/src/livecodes/i18n/locales/ja/translation.ts b/src/livecodes/i18n/locales/ja/translation.ts index 54c2e4354..d0c93956f 100644 --- a/src/livecodes/i18n/locales/ja/translation.ts +++ b/src/livecodes/i18n/locales/ja/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C(Wasm)スターター', ocaml: 'OCamlスターター', 'ocaml-js': 'OCaml(JS)スターター', + 'odin-wasm': 'Odin スターター', pascal: 'Pascalスターター', perl: 'Perlスターター', phaser: 'Phaserスターター', diff --git a/src/livecodes/i18n/locales/nl/language-info.lokalise.json b/src/livecodes/i18n/locales/nl/language-info.lokalise.json index 23635c8f1..7b52c073a 100644 --- a/src/livecodes/i18n/locales/nl/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/nl/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odins eigen compiler, gecompileerd naar WebAssembly, draait volledig in de browser." + }, + "odin-wasm.link": { + "translation": " Officiële website van Odin Overzicht van Odin API van de standaardbibliotheek Leer X in Y minuten, waarbij X = Odin LiveCodes-documentatie Startersjabloon laden " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal draaiend in de browser met pas2js (van het Free Pascal-team), gecompileerd naar WebAssembly." }, diff --git a/src/livecodes/i18n/locales/nl/language-info.ts b/src/livecodes/i18n/locales/nl/language-info.ts index 07f21b04f..e69826894 100644 --- a/src/livecodes/i18n/locales/nl/language-info.ts +++ b/src/livecodes/i18n/locales/nl/language-info.ts @@ -363,6 +363,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml website <3> <4>OCaml documentatie <5> <6>Leer X in Y minuten, waarbij X=OCaml <7> <8>Startsjabloon laden ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odins eigen compiler, gecompileerd naar WebAssembly, draait volledig in de browser.', + link: '<1> <2>Officiële website van Odin <3> <4>Overzicht van Odin <5> <6>API van de standaardbibliotheek <7> <8>Leer X in Y minuten, waarbij X = Odin <9> <10>LiveCodes-documentatie <11> <12>Startersjabloon laden ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal draaiend in de browser met pas2js (van het Free Pascal-team), gecompileerd naar WebAssembly.', link: '<1> <2>Free Pascal officiële website <3> <4>pas2js-documentatie <5> <6>pas2js wiki <7> <8>LiveCodes-documentatie <9> <10>Startsjabloon laden ', diff --git a/src/livecodes/i18n/locales/nl/translation.lokalise.json b/src/livecodes/i18n/locales/nl/translation.lokalise.json index 0bb23e27c..6fd6c46be 100644 --- a/src/livecodes/i18n/locales/nl/translation.lokalise.json +++ b/src/livecodes/i18n/locales/nl/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS)-starter" }, + "templates.starter.odin-wasm": { + "translation": "Odin-starter" + }, "templates.starter.pascal": { "translation": "Pascal-starter" }, diff --git a/src/livecodes/i18n/locales/nl/translation.ts b/src/livecodes/i18n/locales/nl/translation.ts index d802e8809..63c5ec581 100644 --- a/src/livecodes/i18n/locales/nl/translation.ts +++ b/src/livecodes/i18n/locales/nl/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm)-starter', ocaml: 'Ocaml-starter', 'ocaml-js': 'OCaml (JS)-starter', + 'odin-wasm': 'Odin-starter', pascal: 'Pascal-starter', perl: 'Perl-starter', phaser: 'Phaser-starter', diff --git a/src/livecodes/i18n/locales/pt/language-info.lokalise.json b/src/livecodes/i18n/locales/pt/language-info.lokalise.json index 229555b5c..762e0fe91 100644 --- a/src/livecodes/i18n/locales/pt/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/pt/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "O compilador próprio do Odin, compilado para WebAssembly, sendo executado na íntegra no navegador." + }, + "odin-wasm.link": { + "translation": " Site oficial do Odin Visão geral do Odin API da biblioteca padrão Aprenda X em Y minutos, onde X = Odin Documentação do LiveCodes Carregar modelo inicial " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal rodando no navegador usando pas2js (pela equipe do Free Pascal), compilado para WebAssembly." }, diff --git a/src/livecodes/i18n/locales/pt/language-info.ts b/src/livecodes/i18n/locales/pt/language-info.ts index 88c90bdd6..16414ece9 100644 --- a/src/livecodes/i18n/locales/pt/language-info.ts +++ b/src/livecodes/i18n/locales/pt/language-info.ts @@ -363,6 +363,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Site do OCaml <3> <4>Documentação do OCaml <5> <6>Aprenda X em Y minutos, onde X=OCaml <7> <8>Carregar modelo inicial ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'O compilador próprio do Odin, compilado para WebAssembly, sendo executado na íntegra no navegador.', + link: '<1> <2>Site oficial do Odin <3> <4>Visão geral do Odin <5> <6>API da biblioteca padrão <7> <8>Aprenda X em Y minutos, onde X = Odin <9> <10>Documentação do LiveCodes <11> <12>Carregar modelo inicial ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal rodando no navegador usando pas2js (pela equipe do Free Pascal), compilado para WebAssembly.', link: '<1> <2>Site oficial do Free Pascal <3> <4>Documentação do pas2js <5> <6>pas2js wiki <7> <8>Documentação do LiveCodes <9> <10>Carregar modelo inicial ', diff --git a/src/livecodes/i18n/locales/pt/translation.lokalise.json b/src/livecodes/i18n/locales/pt/translation.lokalise.json index 2a4a6f1bd..6fd016435 100644 --- a/src/livecodes/i18n/locales/pt/translation.lokalise.json +++ b/src/livecodes/i18n/locales/pt/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Iniciante OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Iniciante Odin" + }, "templates.starter.pascal": { "translation": "Iniciante Pascal" }, diff --git a/src/livecodes/i18n/locales/pt/translation.ts b/src/livecodes/i18n/locales/pt/translation.ts index 66bff1e29..4af81696f 100644 --- a/src/livecodes/i18n/locales/pt/translation.ts +++ b/src/livecodes/i18n/locales/pt/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Iniciante Objective-C (Wasm)', ocaml: 'Iniciante OCaml', 'ocaml-js': 'Iniciante OCaml (JS)', + 'odin-wasm': 'Iniciante Odin', pascal: 'Iniciante Pascal', perl: 'Iniciante Perl', phaser: 'Iniciante Phaser', diff --git a/src/livecodes/i18n/locales/ru/language-info.lokalise.json b/src/livecodes/i18n/locales/ru/language-info.lokalise.json index b4125d6be..7e12fdb87 100644 --- a/src/livecodes/i18n/locales/ru/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ru/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Собственный компилятор Odin, скомпилированный в WebAssembly, полностью выполняется в браузере." + }, + "odin-wasm.link": { + "translation": " Официальный сайт Odin Обзор Odin API стандартной библиотеки «Изучите X за Y минут», где X = Odin Документация LiveCodes Загрузить стартовый шаблон " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal, работающий в браузере с использованием pas2js (от команды Free Pascal), скомпилирован в WebAssembly." }, diff --git a/src/livecodes/i18n/locales/ru/language-info.ts b/src/livecodes/i18n/locales/ru/language-info.ts index d22bc60a2..73a5ec60f 100644 --- a/src/livecodes/i18n/locales/ru/language-info.ts +++ b/src/livecodes/i18n/locales/ru/language-info.ts @@ -365,6 +365,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>Сайт OCaml <3> <4>Документация OCaml <5> <6>Изучите X за Y минут, где X=OCaml <7> <8>Загрузить стартовый шаблон ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Собственный компилятор Odin, скомпилированный в WebAssembly, полностью выполняется в браузере.', + link: '<1> <2>Официальный сайт Odin <3> <4>Обзор Odin <5> <6>API стандартной библиотеки <7> <8>«Изучите X за Y минут», где X = Odin <9> <10>Документация LiveCodes <11> <12>Загрузить стартовый шаблон ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Pascal, работающий в браузере с использованием pas2js (от команды Free Pascal), скомпилирован в WebAssembly.', link: '<1> <2>Официальный сайт Free Pascal <3> <4>Документация pas2js <5> <6>pas2js wiki <7> <8>Документация LiveCodes <9> <10>Загрузить стартовый шаблон ', diff --git a/src/livecodes/i18n/locales/ru/translation.lokalise.json b/src/livecodes/i18n/locales/ru/translation.lokalise.json index 33358355e..b10f0e896 100644 --- a/src/livecodes/i18n/locales/ru/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ru/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "Стартер OCaml (JS)" }, + "templates.starter.odin-wasm": { + "translation": "Стартер Odin" + }, "templates.starter.pascal": { "translation": "Стартер Pascal" }, diff --git a/src/livecodes/i18n/locales/ru/translation.ts b/src/livecodes/i18n/locales/ru/translation.ts index b85d575c4..d9e2fa957 100644 --- a/src/livecodes/i18n/locales/ru/translation.ts +++ b/src/livecodes/i18n/locales/ru/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Стартер Objective-C (Wasm)', ocaml: 'Стартер OCaml', 'ocaml-js': 'Стартер OCaml (JS)', + 'odin-wasm': 'Стартер Odin', pascal: 'Стартер Pascal', perl: 'Стартер Perl', phaser: 'Стартер Phaser', diff --git a/src/livecodes/i18n/locales/tr/language-info.lokalise.json b/src/livecodes/i18n/locales/tr/language-info.lokalise.json index a5f57575d..07090356f 100644 --- a/src/livecodes/i18n/locales/tr/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/tr/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin'in kendi derleyicisi, WebAssembly'ye derlenmiş, tamamen tarayıcı içinde çalışır." + }, + "odin-wasm.link": { + "translation": " Odin resmi web sitesi Odin'e genel bakış Standart kütüphane API'si Y dakikada X'i öğrenin, burada X=Odin LiveCodes Belgeleri Başlangıç şablonunu yükle " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Pascal, Free Pascal ekibi tarafından pas2js kullanılarak tarayıcıda çalışır ve WebAssembly'ye derlenir." }, diff --git a/src/livecodes/i18n/locales/tr/language-info.ts b/src/livecodes/i18n/locales/tr/language-info.ts index 732cb4871..78f819644 100644 --- a/src/livecodes/i18n/locales/tr/language-info.ts +++ b/src/livecodes/i18n/locales/tr/language-info.ts @@ -364,6 +364,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml web sitesi <3> <4>OCaml dokümantasyonu <5> <6>Y Dakikada X Öğrenin, X=OCaml <7> <8>Başlangıç şablonunu yükle ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: "Odin'in kendi derleyicisi, WebAssembly'ye derlenmiş, tamamen tarayıcı içinde çalışır.", + link: "<1> <2>Odin resmi web sitesi <3> <4>Odin'e genel bakış <5> <6>Standart kütüphane API'si <7> <8>Y dakikada X'i öğrenin, burada X=Odin <9> <10>LiveCodes Belgeleri <11> <12>Başlangıç şablonunu yükle ", + name: 'Odin (Wasm)', + }, pascal: { desc: "Pascal, Free Pascal ekibi tarafından pas2js kullanılarak tarayıcıda çalışır ve WebAssembly'ye derlenir.", link: '<1> <2>Free Pascal resmi web sitesi <3> <4>pas2js dokümantasyonu <5> <6>pas2js wiki <7> <8>LiveCodes Belgeleri <9> <10>Başlangıç şablonunu yükle ', diff --git a/src/livecodes/i18n/locales/tr/translation.lokalise.json b/src/livecodes/i18n/locales/tr/translation.lokalise.json index f4661766e..936ddad18 100644 --- a/src/livecodes/i18n/locales/tr/translation.lokalise.json +++ b/src/livecodes/i18n/locales/tr/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS) Başlangıç" }, + "templates.starter.odin-wasm": { + "translation": "Odin Başlangıç" + }, "templates.starter.pascal": { "translation": "Pascal Başlangıç" }, diff --git a/src/livecodes/i18n/locales/tr/translation.ts b/src/livecodes/i18n/locales/tr/translation.ts index c33e0c0cd..59f318593 100644 --- a/src/livecodes/i18n/locales/tr/translation.ts +++ b/src/livecodes/i18n/locales/tr/translation.ts @@ -1039,6 +1039,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (Wasm) Başlangıç', ocaml: 'Ocaml Başlangıç', 'ocaml-js': 'OCaml (JS) Başlangıç', + 'odin-wasm': 'Odin Başlangıç', pascal: 'Pascal Başlangıç', perl: 'Perl Başlangıç', phaser: 'Phaser Başlangıç', diff --git a/src/livecodes/i18n/locales/ur/language-info.lokalise.json b/src/livecodes/i18n/locales/ur/language-info.lokalise.json index 11cee31c5..d87e16b28 100644 --- a/src/livecodes/i18n/locales/ur/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ur/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin کا اپنا کمپائلر، WebAssembly میں مرتب شدہ، مکمل طور پر براؤزر میں چلتا ہے۔" + }, + "odin-wasm.link": { + "translation": " Odin آفیشل ویب سائٹ Odin کا جائزہ معیاری لائبریری API Y منٹ میں X سیکھیں، جہاں X=Odin LiveCodes دستاویزی لوڈ اسٹارٹر ٹیمپلیٹ " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "Free Pascal ٹیم کے pas2js کا استعمال کرتے ہوئے براؤزر میں چلنے والا Pascal، جو WebAssembly میں مرتب کیا گیا ہے۔" }, diff --git a/src/livecodes/i18n/locales/ur/language-info.ts b/src/livecodes/i18n/locales/ur/language-info.ts index 8ecd10f1f..1956682c8 100644 --- a/src/livecodes/i18n/locales/ur/language-info.ts +++ b/src/livecodes/i18n/locales/ur/language-info.ts @@ -365,6 +365,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>او کیمل ویب سائٹ <3> <4>او کیمل دستاویزات <5> <6>X کو Y منٹ میں سیکھیں، جہاں X=او کیمل <7> <8>ابتدائی سانچہ لوڈ کریں ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odin کا اپنا کمپائلر، WebAssembly میں مرتب شدہ، مکمل طور پر براؤزر میں چلتا ہے۔', + link: '<1> <2>Odin آفیشل ویب سائٹ <3> <4>Odin کا جائزہ <5> <6>معیاری لائبریری API <7> <8>Y منٹ میں X سیکھیں، جہاں X=Odin <9> <10>LiveCodes دستاویزی <11> <12>لوڈ اسٹارٹر ٹیمپلیٹ ', + name: 'Odin (Wasm)', + }, pascal: { desc: 'Free Pascal ٹیم کے pas2js کا استعمال کرتے ہوئے براؤزر میں چلنے والا Pascal، جو WebAssembly میں مرتب کیا گیا ہے۔', link: '<1> <2>Free Pascal کی سرکاری ویب سائٹ <3> <4>pas2js دستاویزات <5> <6>pas2js wiki <7> <8>لائیو کوڈز دستاویزات <9> <10>ابتدائی سانچہ لوڈ کریں ', diff --git a/src/livecodes/i18n/locales/ur/translation.lokalise.json b/src/livecodes/i18n/locales/ur/translation.lokalise.json index 4d466e54a..cb54f11c3 100644 --- a/src/livecodes/i18n/locales/ur/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ur/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml (JS) شروعاتی" }, + "templates.starter.odin-wasm": { + "translation": "Odin شروعاتی" + }, "templates.starter.pascal": { "translation": "پاسکل شروعاتی" }, diff --git a/src/livecodes/i18n/locales/ur/translation.ts b/src/livecodes/i18n/locales/ur/translation.ts index 88479ceb5..024962bf3 100644 --- a/src/livecodes/i18n/locales/ur/translation.ts +++ b/src/livecodes/i18n/locales/ur/translation.ts @@ -1038,6 +1038,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C (واسم) شروعاتی', ocaml: 'اوکامل شروعاتی', 'ocaml-js': 'OCaml (JS) شروعاتی', + 'odin-wasm': 'Odin شروعاتی', pascal: 'پاسکل شروعاتی', perl: 'پرل شروعاتی', phaser: 'فیزر شروعاتی', diff --git a/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json b/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json index d42dabcb5..0f9bc4fb8 100644 --- a/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json @@ -648,6 +648,15 @@ "ocamlJs.name": { "translation": "OCaml (JS)" }, + "odin-wasm.desc": { + "translation": "Odin 自带的编译器,编译为 WebAssembly,完全在浏览器中运行。" + }, + "odin-wasm.link": { + "translation": " Odin 官方网站 Odin 概览 标准库 API 《Y分钟学会X》,其中X=Odin LiveCodes 文档 加载入门模板 " + }, + "odin-wasm.name": { + "translation": "Odin (Wasm)" + }, "pascal.desc": { "translation": "使用 Free Pascal 团队的 pas2js 在浏览器中运行的 Pascal,并编译为 WebAssembly。" }, diff --git a/src/livecodes/i18n/locales/zh-CN/language-info.ts b/src/livecodes/i18n/locales/zh-CN/language-info.ts index ef1434308..85524aa7b 100644 --- a/src/livecodes/i18n/locales/zh-CN/language-info.ts +++ b/src/livecodes/i18n/locales/zh-CN/language-info.ts @@ -356,6 +356,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1><2>OCaml 官方网站 <3> <4>OCaml 文档 <5> <6>在 Y 分钟内学习 X,其中 X=OCaml <7> <8>加载启动模板 ', name: 'OCaml (JS)', }, + 'odin-wasm': { + desc: 'Odin 自带的编译器,编译为 WebAssembly,完全在浏览器中运行。', + link: '<1> <2>Odin 官方网站 <3> <4>Odin 概览 <5> <6>标准库 API <7> <8>《Y分钟学会X》,其中X=Odin <9> <10>LiveCodes 文档 <11> <12>加载入门模板 ', + name: 'Odin (Wasm)', + }, pascal: { desc: '使用 Free Pascal 团队的 pas2js 在浏览器中运行的 Pascal,并编译为 WebAssembly。', link: '<1> <2>Free Pascal 官网 <3> <4>pas2js 文档 <5> <6>pas2js wiki <7> <8>LiveCodes 文档 <9> <10>加载启动模板 ', diff --git a/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json b/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json index ed3cdfc3a..9ded55011 100644 --- a/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json +++ b/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json @@ -2100,6 +2100,9 @@ "templates.starter.ocaml-js": { "translation": "OCaml(JS)启动模板" }, + "templates.starter.odin-wasm": { + "translation": "Odin 启动模板" + }, "templates.starter.pascal": { "translation": "Pascal 启动模板" }, diff --git a/src/livecodes/i18n/locales/zh-CN/translation.ts b/src/livecodes/i18n/locales/zh-CN/translation.ts index 6566987f1..1d141f848 100644 --- a/src/livecodes/i18n/locales/zh-CN/translation.ts +++ b/src/livecodes/i18n/locales/zh-CN/translation.ts @@ -1036,6 +1036,7 @@ const translation: I18nTranslation = { 'objc-wasm': 'Objective-C(WASM)启动模板', ocaml: 'Ocaml 启动模板', 'ocaml-js': 'OCaml(JS)启动模板', + 'odin-wasm': 'Odin 启动模板', pascal: 'Pascal 启动模板', perl: 'Perl 启动模板', phaser: 'Phaser 启动模板', diff --git a/src/livecodes/languages/languages.ts b/src/livecodes/languages/languages.ts index 72b5ac0b3..3fdabafea 100644 --- a/src/livecodes/languages/languages.ts +++ b/src/livecodes/languages/languages.ts @@ -66,6 +66,7 @@ import { nim, nimWasm } from './nim'; import { nunjucks } from './nunjucks'; import { ocaml } from './ocaml'; import { ocamlJs } from './ocaml-js'; +import { odinWasm } from './odin-wasm'; import { pascal } from './pascal'; import { perl } from './perl'; import { php } from './php'; @@ -170,6 +171,7 @@ export const languages: LanguageSpecs[] = [ nim, nimWasm, crystalWasm, + odinWasm, janetWasm, python, pythonWasm, diff --git a/src/livecodes/languages/odin-wasm/__tests__/lang-odin-wasm.spec.ts b/src/livecodes/languages/odin-wasm/__tests__/lang-odin-wasm.spec.ts new file mode 100644 index 000000000..b40408de7 --- /dev/null +++ b/src/livecodes/languages/odin-wasm/__tests__/lang-odin-wasm.spec.ts @@ -0,0 +1,38 @@ +import { odinWasm } from '../lang-odin-wasm'; + +describe('Odin (Wasm) language spec', () => { + test('is registered as odin-wasm, with the wasm compiler script', () => { + expect(odinWasm.name).toBe('odin-wasm'); + expect(odinWasm.title).toBe('Odin (Wasm)'); + expect(odinWasm.compiler).toMatchObject({ + scriptType: 'text/odin-wasm', + compiledCodeLanguage: 'odin-wasm', + liveReload: true, + }); + }); + + test('accepts the Odin extension', () => { + expect(odinWasm.extensions).toEqual(expect.arrayContaining(['odin'])); + }); + + test('warns that the first run is a large download', () => { + // The compiler and the standard library are ~6 MB compressed; the app shows a loading + // indicator while they arrive. + expect(odinWasm.largeDownload).toBe(true); + }); + + test('loads the compiler script the build produces', () => { + const compiler = odinWasm.compiler as unknown as { + scripts: (options: { baseUrl: string }) => string[]; + }; + expect(compiler.scripts({ baseUrl: '/' })).toEqual(['/{{hash:lang-odin-wasm-script.js}}']); + }); + + test('highlights with the Odin grammars', () => { + expect(odinWasm.editorSupport?.monaco).toMatchObject({ language: 'odin' }); + expect(odinWasm.editorSupport?.codemirror).toMatchObject({ + languageSupport: expect.any(Function), + }); + expect(odinWasm.editorSupport?.codejar).toEqual({ language: 'odin' }); + }); +}); diff --git a/src/livecodes/languages/odin-wasm/index.ts b/src/livecodes/languages/odin-wasm/index.ts new file mode 100644 index 000000000..99354402b --- /dev/null +++ b/src/livecodes/languages/odin-wasm/index.ts @@ -0,0 +1 @@ +export * from './lang-odin-wasm'; diff --git a/src/livecodes/languages/odin-wasm/lang-odin-wasm-script.ts b/src/livecodes/languages/odin-wasm/lang-odin-wasm-script.ts new file mode 100644 index 000000000..11e54e344 --- /dev/null +++ b/src/livecodes/languages/odin-wasm/lang-odin-wasm-script.ts @@ -0,0 +1,368 @@ +/* eslint-disable no-console */ +import { getErrorMessage } from '../../utils/utils'; +import { odinWasmBaseUrl } from '../../vendors'; +import { createLoadingReporter, runCompiler } from '../wasm-runtime'; +import { createWorkerRunner, type Runner } from '../worker-runner'; + +// Odin's compiler is a WASI command module, built by wodin — the fork of Odin with a direct +// WebAssembly backend that emits wasm with no LLVM and no linker — and packaged with the standard +// library. The package (`@live-codes/odin-wasm`) is fetched from the CDN. +// +// Where the *program* runs depends on the target it was built for: +// +// wasi_wasm32 (the default) a WASI host inside the package, in this worker — which is what makes +// stdin, stdout, stderr and the exit code work. +// js_wasm32 this page, on Odin's own JS runtime. A worker has no `document`, and +// that runtime is written for the page, so a program that touches the +// DOM or a canvas can only run here. + +const SCRIPT_TYPE = 'text/odin-wasm'; +const DEFAULT_TARGET = 'wasi_wasm32'; + +// A single compile + run is normally quick; a long timeout guards against hangs (e.g. an infinite +// loop in the user program) by killing and respawning the worker. The wasm call cannot be +// interrupted any other way. +const RUN_TIMEOUT_MS = 120000; + +/** + * Options read from `config.customSettings`: `compileArgs` and `target` are fixed when the compiler + * is created, while `args` is a per-run option. + */ +interface OdinWasmSettings { + compileArgs?: string[]; + args?: string[]; + target?: string; +} + +interface RunResult { + input: string; + output: string | null; + error: string | null; + exitCode: number | null; +} + +interface OdinWasmApi { + ready: boolean; + failed: boolean; + input: string; + output: string | null; + error: string | null; + exitCode: number | null; + loaded: Promise; + init: Promise | null; + runner?: Runner; + settings?: Record; + run: (input?: string) => Promise; +} + +/** The compiled module, as a `js_wasm32` compile hands it back. */ +interface CompiledModule { + wasm?: ArrayBuffer | null; + errors?: string[]; +} + +declare const window: Window & { + livecodes: Record; +}; + +const getWorkerSrc = () => ` +importScripts(${JSON.stringify(odinWasmBaseUrl + 'dist/odin-wasm.global.js')}); + +let compiler = null; +let compilerOptions = null; + +const getCompiler = (options = {}) => { + const key = JSON.stringify([options.compileArgs, options.target]); + if (compiler && compilerOptions === key) return compiler; + compilerOptions = key; + const previous = compiler; + const created = (async () => { + const next = await self.odinWasm.createCompiler({ + baseUrl: ${JSON.stringify(odinWasmBaseUrl + 'assets/')}, + compileArgs: options.compileArgs || [], + target: options.target, + }); + // \`compileArgs\` and \`target\` are fixed when a compiler is created, so a change builds a new + // one and lets the old go; its assets are the browser's cache to keep. + if (previous) previous.then((old) => old.dispose()).catch(() => undefined); + return next; + })(); + created.catch(() => { + if (compiler === created) { + compiler = null; + compilerOptions = null; + } + }); + compiler = created; + return compiler; +}; + +addEventListener('message', async (event) => { + const { id, code, input, options } = event.data; + try { + const compiler = await getCompiler(options); + // The js target is compiled here but run by the page, which is the only place with a DOM. + if (options && options.kind === 'compile') { + const compiled = await compiler.compile(code, options); + const bytes = compiled.wasm ? compiled.wasm.slice() : null; + postMessage( + { id, result: { wasm: bytes ? bytes.buffer : null, errors: compiled.errors } }, + bytes ? [bytes.buffer] : [], + ); + return; + } + const result = await compiler.run(code, input, { args: options && options.args }); + postMessage({ + id, + result: { + output: result.output, + // The compiler's diagnostics; empty when the program built. + errors: result.errors, + exitCode: result.exitCode, + }, + }); + } catch (error) { + postMessage({ id, error: String((error && error.message) || error) }); + } +}); + +getCompiler().then( + () => postMessage({ type: 'ready' }), + (error) => postMessage({ type: 'error', message: String((error && error.message) || error) }), +); +`; + +const reportLoading = createLoadingReporter(); + +/** + * Records a run and, unless the caller logs the output itself (the js target streams it), reports it. + */ +const report = ( + input: string, + output: string | null, + error: string | null, + exitCode: number | null, + log = true, +): RunResult => { + odinWasm.input = input; + odinWasm.output = output; + odinWasm.error = error; + odinWasm.exitCode = exitCode; + odinWasm.ready = true; + + if (log) { + if (error != null) { + console.error(error); + } else if (output != null) { + console.log(output); + } + } + return { input, output, error, exitCode }; +}; + +window.livecodes.odinWasm ??= {} as OdinWasmApi; + +const odinWasm = window.livecodes.odinWasm; +odinWasm.ready = false; +odinWasm.failed = false; + +/** The custom settings of the running language, injected by the language's `inlineScript`. */ +const getSettings = (): OdinWasmSettings => { + const settings = odinWasm.settings?.['odin-wasm']; + return settings != null && typeof settings === 'object' ? settings : {}; +}; + +/** The program, which the result page writes inline as a `text/odin-wasm` script. */ +const getCode = () => { + let code = ''; + document.querySelectorAll(`script[type="${SCRIPT_TYPE}"]`).forEach((script) => { + code += `${script.textContent ?? ''}\n`; + }); + return code; +}; + +/** Start (once) downloading the compiler, showing the loading indicator while it happens. */ +const ensureLoaded = (runner: Runner): Promise => { + let init = odinWasm.init; + if (!init) { + odinWasm.failed = false; + init = (async () => { + reportLoading(true); + try { + await runner.ensureReady(); + } finally { + reportLoading(false); + } + })().catch((error: Error) => { + // Reset so a later run can retry the download, and let `loaded` reject. + odinWasm.init = null; + odinWasm.failed = true; + throw error; + }); + // The failure is surfaced through `run`; do not also report it unhandled. + init.catch(() => undefined); + odinWasm.init = init; + } + return init; +}; + +/** Odin's own JS runtime, loaded into this page once. */ +let odinRuntime: Promise | null = null; +const loadOdinRuntime = (): Promise => { + if ((window as any).odin) return Promise.resolve((window as any).odin); + odinRuntime ??= new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = odinWasmBaseUrl + 'assets/odin.js'; + script.onload = () => resolve((window as any).odin); + script.onerror = () => reject(new Error('Failed to load the Odin JS runtime (assets/odin.js)')); + document.head.appendChild(script); + }); + // A failed load must not be cached, so a later run can retry it. + odinRuntime.catch(() => { + odinRuntime = null; + }); + return odinRuntime; +}; + +/** + * Instantiate a `js_wasm32` module on this page with Odin's runtime, so the program reaches the DOM + * and a canvas. Output is streamed to the console as the program writes it; a program that exports + * `step` is driven once per animation frame, as Odin's own runtime does. + */ +const runModuleOnPage = async (wasm: Uint8Array, input: string): Promise => { + const odin = await loadOdinRuntime(); + const memory = new odin.WasmMemoryInterface(); + memory.setIntSize(4); + const imports = odin.setupDefaultImports(memory, null, memory.memory); + + const inputBytes = new TextEncoder().encode(input); + let inputOffset = 0; + const output: string[] = []; + + imports.odin_env.write = (fd: number, ptr: number, len: number) => { + const text = memory.loadString(ptr, len); + if (fd === 2) { + console.error(text); + } else { + output.push(text); + console.log(text); + } + }; + imports.odin_env.read = (fd: number, ptr: number, len: number) => { + if (fd !== 0) return 0; + const count = Math.max(0, Math.min(len, inputBytes.length - inputOffset)); + if (count > 0) { + new Uint8Array(memory.memory.buffer, ptr, count).set( + inputBytes.subarray(inputOffset, inputOffset + count), + ); + inputOffset += count; + } + return count; + }; + + const { instance } = await WebAssembly.instantiate(wasm.slice(), imports); + const exports = instance.exports as Record; + memory.setExports(exports); + if (exports.memory) memory.setMemory(exports.memory); + + /** Call one of the runtime's own entry points by name (`_start`, `_end`) when it is there. */ + const call = (name: string) => { + const fn = exports[name]; + return typeof fn === 'function' ? fn() : undefined; + }; + + const finish = (exitCode: number, error: string | null = null) => + report(input, output.join(''), error, exitCode, /* log = */ false); + + try { + call('_start'); + } catch (error) { + return finish(1, `The program trapped: ${getErrorMessage(error)}`); + } + + if (exports.step) { + // An interactive program: drive it once per frame, and let it keep writing to the console. + const context = exports.default_context_ptr ? exports.default_context_ptr() : 0; + let previous: number | undefined; + const frame = (timestamp: number) => { + if (previous === undefined) previous = timestamp; + const dt = (timestamp - previous) * 0.001; + previous = timestamp; + let keepGoing = false; + try { + keepGoing = exports.step(dt, context); + } catch (error) { + odinWasm.error = `The program trapped: ${getErrorMessage(error)}`; + console.error(odinWasm.error); + return; + } + if (!keepGoing) { + call('_end'); + odinWasm.output = output.join(''); + return; + } + requestAnimationFrame(frame); + }; + requestAnimationFrame(frame); + return finish(0); + } + + call('_end'); + return finish(0); +}; + +/** Compile in the worker, then run the module here — the only place with a DOM. */ +const runOnPage = async ( + code: string, + input: string, + settings: OdinWasmSettings, + runner: Runner, +): Promise => { + try { + await ensureLoaded(runner); + const compiled = (await runner.run(code, '', { + ...settings, + kind: 'compile', + })) as unknown as CompiledModule; + if (!compiled?.wasm) { + return report(input, null, (compiled?.errors ?? ['Odin produced no module']).join('\n'), 1); + } + return await runModuleOnPage(new Uint8Array(compiled.wasm), input); + } catch (error) { + return report(input, null, `Error: ${getErrorMessage(error)}`, 1); + } +}; + +odinWasm.loaded = new Promise((resolve, reject) => { + const interval = setInterval(() => { + if (odinWasm.failed) { + clearInterval(interval); + reject(new Error(odinWasm.error || 'Failed to initialize the Odin compiler')); + } else if (odinWasm.ready) { + clearInterval(interval); + resolve(); + } + }, 50); +}); + +odinWasm.run = async (input?: string) => { + const stdin = `${input ?? odinWasm.input ?? ''}`; + const code = getCode(); + if (!code.trim()) return report(stdin, null, null, null); + + const settings = getSettings(); + const runner = (odinWasm.runner = + odinWasm.runner || + createWorkerRunner({ getWorkerSrc, label: 'Odin', timeoutMs: RUN_TIMEOUT_MS })); + + if ((settings.target ?? DEFAULT_TARGET) === 'js_wasm32') { + return runOnPage(code, stdin, settings, runner); + } + return runCompiler(runner, ensureLoaded, code, stdin, report, settings); +}; + +window.addEventListener('load', async () => { + reportLoading(true); + await odinWasm.run(odinWasm.input); + reportLoading(false); +}); diff --git a/src/livecodes/languages/odin-wasm/lang-odin-wasm.ts b/src/livecodes/languages/odin-wasm/lang-odin-wasm.ts new file mode 100644 index 000000000..c484638a5 --- /dev/null +++ b/src/livecodes/languages/odin-wasm/lang-odin-wasm.ts @@ -0,0 +1,36 @@ +import type { Config, LanguageSpecs } from '../../models'; +import { getLanguageCustomSettings } from '../../utils'; +import { codeMirrorBaseUrl, monacoLanguagesBaseUrl } from '../../vendors'; + +const getSettingsScript = (config: Config) => { + const settings = getLanguageCustomSettings('odin-wasm', config); + // `\u003c` keeps a `` in a setting (e.g. a compile argument) from closing the tag. + const serialized = JSON.stringify(settings).replace(/ async (code) => code, + scripts: ({ baseUrl }) => [baseUrl + '{{hash:lang-odin-wasm-script.js}}'], + inlineScript: ({ config }) => getSettingsScript(config), + scriptType: 'text/odin-wasm', + compiledCodeLanguage: 'odin-wasm', + liveReload: true, + }, + extensions: ['odin'], + editor: 'script', + editorSupport: { + monaco: { languageSupport: monacoLanguagesBaseUrl + 'odin.js', language: 'odin' }, + codemirror: { + languageSupport: async () => + (await import(codeMirrorBaseUrl + 'codemirror-lang-odin.js')).odin(), + }, + codejar: { language: 'odin' }, + }, + largeDownload: true, +}; diff --git a/src/livecodes/models.ts b/src/livecodes/models.ts index d4e3ca09a..c34664f85 100644 --- a/src/livecodes/models.ts +++ b/src/livecodes/models.ts @@ -208,6 +208,7 @@ export interface Compiler { | 'text/cobol-wasm' | 'text/rust-wasm' | 'text/crystal-wasm' + | 'text/odin-wasm' | 'text/zig-wasm' | 'text/java' | 'text/csharp-wasm' @@ -297,6 +298,7 @@ export type TemplateAlias = | 'janet' | 'mzn' | 'crystal' + | 'odin' | 'dartlang'; export type Template = Pick & diff --git a/src/livecodes/templates/starter/index.ts b/src/livecodes/templates/starter/index.ts index 4fc6bd22a..c23b07f6e 100644 --- a/src/livecodes/templates/starter/index.ts +++ b/src/livecodes/templates/starter/index.ts @@ -60,6 +60,7 @@ import { nimStarter } from './nim-starter'; import { nimWasmStarter } from './nim-wasm-starter'; import { ocamlJsStarter } from './ocaml-js-starter'; import { ocamlStarter } from './ocaml-starter'; +import { odinWasmStarter } from './odin-wasm-starter'; import { pascalStarter } from './pascal-starter'; import { perlStarter } from './perl-starter'; import { phaserStarter } from './phaser-starter'; @@ -140,6 +141,7 @@ export const starterTemplates = [ nimStarter, nimWasmStarter, crystalWasmStarter, + odinWasmStarter, janetWasmStarter, pythonStarter, pythonWasmStarter, diff --git a/src/livecodes/templates/starter/odin-wasm-starter.ts b/src/livecodes/templates/starter/odin-wasm-starter.ts new file mode 100644 index 000000000..3ba169df3 --- /dev/null +++ b/src/livecodes/templates/starter/odin-wasm-starter.ts @@ -0,0 +1,110 @@ +import type { Template } from '../../models'; + +export const odinWasmStarter: Template = { + name: 'odin-wasm', + aliases: ['odin'], + title: window.deps.translateString('templates.starter.odin-wasm', 'Odin Starter'), + thumbnail: 'assets/templates/odin.svg', + activeEditor: 'script', + markup: { + language: 'html', + content: ` +
    +

    Hello, World!

    + +

    You clicked 0 times.

    + +
    + + +`.trimStart(), + }, + style: { + language: 'css', + content: ` +.container, +.container button { + text-align: center; + font: 1em sans-serif; +} +.logo { + width: 150px; +} +`.trimStart(), + }, + script: { + language: 'odin', + content: ` +package main + +import "core:bufio" +import "core:fmt" +import "core:os" +import "core:strconv" +import "core:strings" + +main :: proc() { + reader: bufio.Reader + bufio.reader_init(&reader, os.to_reader(os.stdin)) + defer bufio.reader_destroy(&reader) + + fmt.println("Odin") + + count := -1 + for { + line, err := bufio.reader_read_string(&reader, '\\n') + // The last line may have no trailing newline, and that comes back as an + // error together with the data — read the data first, then stop. + if len(line) == 0 { + break + } + if value, ok := strconv.parse_int(strings.trim_space(line)); ok { + count = value + } + if err != nil { + break + } + } + fmt.println(count + 1) +} +`.trimStart(), + }, +}; diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 2aa4ae923..0993e9886 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -64,7 +64,7 @@ export const cjs2esUrl = /* @__PURE__ */ getUrl('cjs2es@1.1.1/dist/cjs2es.browse export const clangWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.3.0/'); -export const crystalWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/crystal-wasm@0.2.0/'); +export const crystalWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/crystal-wasm@0.3.0/'); export const clioBaseUrl = /* @__PURE__ */ getUrl( '@live-codes/clio-browser-compiler@0.0.3/public/build/', @@ -110,7 +110,7 @@ export const codeiumProviderUrl = /* @__PURE__ */ getUrl( '@live-codes/monaco-codeium-provider@0.2.2/dist/index.js', ); -export const codeMirrorBaseUrl = /* @__PURE__ */ getUrl('@live-codes/codemirror@0.4.5/build/'); +export const codeMirrorBaseUrl = /* @__PURE__ */ getUrl('@live-codes/codemirror@0.4.6/build/'); export const codemirrorMinimapUrl = /* @__PURE__ */ getUrl( '@replit/codemirror-minimap@0.5.2/dist/index.js', @@ -372,7 +372,7 @@ export const monacoBaseUrl = /* @__PURE__ */ getUrl('@live-codes/monaco-editor@0 export const monacoEmacsUrl = /* @__PURE__ */ getUrl('monaco-emacs@0.3.0/dist/monaco-emacs.js'); export const monacoLanguagesBaseUrl = /* @__PURE__ */ getUrl( - '@live-codes/monaco-languages@0.3.13/dist/', + '@live-codes/monaco-languages@0.3.14/dist/', ); export const monacoThemesBaseUrl = /* @__PURE__ */ getUrl('monaco-themes@0.4.4/themes/'); @@ -479,6 +479,8 @@ export const reasonStdLibBaseUrl = /* @__PURE__ */ getUrl('@rescript/std@9.1.3/l export const ocamlJsBaseUrl = /* @__PURE__ */ getUrl('@live-codes/ocaml@0.2.0/'); +export const odinWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/odin-wasm@0.2.0/'); + export const rescriptCdnBaseUrl = /* @__PURE__ */ getUrl('https://cdn.rescript-lang.org/v11.1.2/'); export const rescriptStdLibBaseUrl = /* @__PURE__ */ getUrl('@rescript/std@11.1.2/lib/es6/'); diff --git a/src/sdk/models.ts b/src/sdk/models.ts index 9b29b0bda..a9f49980a 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -323,6 +323,8 @@ export type Language = | 'cr' | 'crystal' | 'crystal-wasm' + | 'odin' + | 'odin-wasm' | 'nim' | 'nims' | 'nimble' @@ -530,6 +532,7 @@ export type TemplateName = | 'fortran-wasm' | 'd-wasm' | 'crystal-wasm' + | 'odin-wasm' | 'cobol-wasm' | 'v-wasm' | 'java' diff --git a/storybook/_stories/EmbedOptions/template.ts b/storybook/_stories/EmbedOptions/template.ts index 12d299bfc..59fbed84e 100644 --- a/storybook/_stories/EmbedOptions/template.ts +++ b/storybook/_stories/EmbedOptions/template.ts @@ -55,6 +55,7 @@ const storyDef: StoryDef = { CWasm: { props: { template: 'c-wasm' } }, CppWasm: { props: { template: 'cpp-wasm' } }, CrystalWasm: { props: { template: 'crystal-wasm' }, storyName: 'Crystal (Wasm)' }, + OdinWasm: { props: { template: 'odin-wasm' }, storyName: 'Odin (Wasm)' }, ObjcWasm: { props: { template: 'objc-wasm' } }, DWasm: { props: { template: 'd-wasm' } }, CobolWasm: { props: { template: 'cobol-wasm' }, storyName: 'COBOL (Wasm)' }, diff --git a/storybook/preact/stories/EmbedOptions/template.stories.ts b/storybook/preact/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/preact/stories/EmbedOptions/template.stories.ts +++ b/storybook/preact/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/storybook/react/stories/EmbedOptions/template.stories.ts b/storybook/react/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/react/stories/EmbedOptions/template.stories.ts +++ b/storybook/react/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/storybook/solid/stories/EmbedOptions/template.stories.ts b/storybook/solid/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/solid/stories/EmbedOptions/template.stories.ts +++ b/storybook/solid/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/storybook/svelte/stories/EmbedOptions/template.stories.ts b/storybook/svelte/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/svelte/stories/EmbedOptions/template.stories.ts +++ b/storybook/svelte/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/storybook/vue/stories/EmbedOptions/template.stories.ts b/storybook/vue/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/vue/stories/EmbedOptions/template.stories.ts +++ b/storybook/vue/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/storybook/web-components/stories/EmbedOptions/template.stories.ts b/storybook/web-components/stories/EmbedOptions/template.stories.ts index a0136e2ac..138f6369a 100644 --- a/storybook/web-components/stories/EmbedOptions/template.stories.ts +++ b/storybook/web-components/stories/EmbedOptions/template.stories.ts @@ -69,6 +69,8 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const CrystalWasm = livecodesStory({ template: 'crystal-wasm' }); CrystalWasm.storyName = 'Crystal (Wasm)'; +export const OdinWasm = livecodesStory({ template: 'odin-wasm' }); +OdinWasm.storyName = 'Odin (Wasm)'; export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const DWasm = livecodesStory({ template: 'd-wasm' }); export const CobolWasm = livecodesStory({ template: 'cobol-wasm' }); diff --git a/vendor-licenses.md b/vendor-licenses.md index a7841eded..856b4f70d 100644 --- a/vendor-licenses.md +++ b/vendor-licenses.md @@ -360,6 +360,8 @@ wacl: [BSD 3-Clause License](https://github.com/ecky-l/wacl/blob/9daacabb0102a99 Crystal compiler and standard library: [Apache-2.0 License](https://github.com/crystal-lang/crystal/blob/master/LICENSE) +Odin compiler and standard library: [zlib License](https://github.com/odin-lang/Odin/blob/e87445a582ba74f526670ed94d68d9be95de2f8b/LICENSE) + LLVM and the wasm sysroot libraries: [Apache-2.0 WITH LLVM-exception](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) and [MIT](https://github.com/WebAssembly/wasi-libc/blob/main/LICENSE.md) @live-codes/dlang-wasm: [MIT License](https://github.com/live-codes/browser-d/blob/main/LICENSE)