Architecture
Components
Section titled “Components”Excel for Mac └─ manifest.xml (sideloaded into .../Containers/com.microsoft.Excel/.../wef) ├─ taskpane.html ← setup/help pane └─ CustomFunctions extension point ├─ functions.html (page the runtime loads) ├─ functions.js (implementations, namespace XLT) └─ functions.json (metadata: names, params, dimensionality)
scripts/dev_server.py HTTPS host on :3000 ├─ static files from web/ ├─ GET /api/health └─ POST /api/translate ──▶ LM Studio /v1/chat/completions /v1/modelsRepository layout
Section titled “Repository layout”| Path | Role |
|---|---|
manifest.xml |
Task-pane add-in manifest declaring the XLT namespace, the CustomFunctionsRuntime 1.1 requirement, and https://localhost:3000 as an AppDomain |
web/taskpane.html / .css / .js |
Setup and help pane with a “Check local host” button that calls /api/health |
web/functions.html |
Minimal page the custom-functions runtime loads; pulls in office.js and functions.js |
web/functions.js |
TRANSLATE and TRANSLATE_RANGE, registered with CustomFunctions.associate |
web/functions.json |
Custom-function metadata consumed by Excel |
scripts/dev_server.py |
HTTPS static host plus translation proxy, standard library only |
scripts/install_manifest.sh |
Copies the manifest into Excel’s macOS sideload folder |
scripts/trust-localhost-cert.sh |
Adds certs/localhost.pem to the login keychain as a trusted root |
scripts/reset-excel-cache.sh |
Clears Excel’s sideload and web caches so a changed manifest can re-register |
scripts/docker-*.sh |
Thin wrappers around docker compose |
Dockerfile, docker-compose.yml |
Containerised host based on python:3.12-slim |
docs/ |
This Astro Starlight documentation site |
Request path for one formula
Section titled “Request path for one formula”- A cell evaluates
=XLT.TRANSLATE(A2,"de"). Excel calls thetranslatefunction inweb/functions.js. - The function trims the input, resolves the target language, defaults the source language to
auto, and returns""immediately when the text is empty. - It
POSTs to the relative URL/api/translate— the add-in is served fromhttps://localhost:3000, so the request goes back to the same host. AddInHandler.do_POSTvalidates the payload:targetLanguagemust be present,textsmust be a non-empty list, and at most 128 values are accepted._get_lm_studio_modelreturnsLM_STUDIO_MODELif set; otherwise it callsGET /v1/modelsand takes the first id._translate_with_lm_studioloops over the values, sending onePOST /v1/chat/completionsper value with a translation system prompt andtemperature: 0.1.- The assistant content of each response is trimmed and collected, then returned as
{"translations": [...]}. functions.jsnormalises the strings and hands them back to Excel.
TRANSLATE_RANGE follows the same path, except it flattens the matrix into non-empty values with
recorded positions, splits them into chunks of 64 per request, and reassembles a matrix of the
original shape for Excel to spill.
Design choices
Section titled “Design choices”No build tooling. The add-in payload is static HTML/CSS/JS loaded directly by Excel’s webview.
There is no bundler, no TypeScript compile step, and no node_modules for the add-in itself — the
root package.json exists only to name the npm scripts.
Python standard library only. dev_server.py uses http.server, ssl, json and urllib. No
pip install step and no virtualenv are needed for the server to run.
HTTPS is mandatory, so certificates are the user’s job. Office Add-ins are loaded over HTTPS, and
Excel refuses an untrusted certificate. Rather than shipping a key pair — which would be a published
private key anyone could use to impersonate localhost on a machine that trusted it — the repository
ships no key material at all, and the server refuses to start until you generate your own.
Same-origin proxy instead of direct calls. The functions request the relative path
/api/translate, so everything stays on the add-in’s own HTTPS origin. That keeps LM Studio’s plain
HTTP endpoint out of the webview (where it would be mixed content) and concentrates all configuration
in one process.
Errors as text, not Excel error values. Failures are returned as [translation failed: ...]
strings so the cause is readable in the cell rather than collapsing to #VALUE!.
Known limitations
Section titled “Known limitations”- Translations are issued one model call per cell, sequentially. There is no batching into a single prompt and no concurrency, so throughput is bounded by model latency times the cell count.
- No caching. Recalculating a formula re-translates the text.
/api/healthand/api/translateare unauthenticated. The host is intended for loopback use only.web/functions.jssends aprovider: "lm_studio"field in the request body; the server ignores it and always uses LM Studio.- The host and port appear in
manifest.xmlandweb/functions.jsonas literalhttps://localhost:3000URLs, so changingPORTmeans editing those files too.