Preprocessing app for a numerical simulator

Hello the Dash community,

Just wanted to share an application I developed using Dash. You will likely never have to use it since it’s a pre-processor for a numerical simulator developed by my lab, but I guess it shows the versatility of Dash.
Here is the link: https://toughio-dash.lbl.gov

Some key features:

  • Create new input file from scratch, using a sample template or upload custom input files to edit.
  • Live update (right pane) of the expected output (like Overleaf for those who know it) where each block is independently updated.
  • Every divs is there but not displayed (display: none), clicking on a menu item change their styles to display: block (straightforward to do using a single pattern matching callback).
  • Generate meshes and visualize them using dash-vtk.
  • Translate the app anytime anywhere within the app (only English and French available, currently).

I am no web developer, so if you have any suggestion, don’t hesitate!

1 Like

Way to go, @Kefeng. And thank you for sharing with the community.

I like this app a lot, especially the layout you built.

How did you implement the translation part?

Thanks!

Here is the localization module.

import glob
import json

from dash.dependencies import Input, Output, State, ALL

from app import app


id_to_key = {}


def id_to_str(id):
    if isinstance(id, str):
        return id

    tmp = [f'"{key}":"{id[key]}"' for key in sorted(id)]

    return f"{{{','.join(tmp)}}}"


def register(ids):
    id_to_key.update(ids)


def translate(key=None, id=None, lang="en-us"):
    if key or id:
        key = key if key else id_to_key[id_to_str(id)]

        try:
            return languages[lang][key]

        except KeyError:
            try:
                print(f"Key '{key}' undefined for lang '{lang}'")
                return languages["en-us"][key]

            except KeyError:
                raise KeyError(f"Key '{key}' undefined for default lang 'en-us'")

    else:
        raise ValueError()


def read_index():
    with open("lang/index.json", "r", encoding="utf8") as f:
        index = json.load(f)

    return index


def read_lang(filename):
    with open(filename, "r", encoding="utf8") as f:
        lang = json.load(f)

    return {
        k: v.replace("[", "\[")
        .replace("]", "\]")
        .replace(">", "\>")
        .replace("sub\>", "sub>")
        .replace("sup\>", "sup>")
        for k, v in lang.items()
    }


def initialize_langs(index):
    langs = {}
    for k in index:
        langs[k] = {}

        for filename in glob.glob(f"lang/{k}/*.json"):
            langs[k].update(read_lang(filename))

    return langs


lang_index = read_index()
languages = initialize_langs(lang_index)


@app.callback(
    Output({"type": "text", "file": ALL, "index": ALL}, "children"),
    Input("language_dropdown", "value"),
    State({"type": "text", "file": ALL, "index": ALL}, "id"),
    State("debug", "data"),
    prevent_initial_call=True,
)
def update_translation(lang, text_ids, debug):
    if debug:
        for id in text_ids:
            id = id_to_str(id)

            if id not in id_to_key:
                print(id)

    return [translate(id=id, lang=lang) for id in text_ids]

Every string is stored as a pair “key: my_translation” in a bunch of JSON files organized as follows:

lang
├─ index.json
├─ en-us
|  ├─ file1.json
|  ├─ file2.json
|  ├─ ...
├─ fr-fr
|  ├─ file1.json
|  ├─ file2.json


where index.json looks like this:

{
    "en-us": "English",
    "fr-fr": "Français"
}

To initialize a string, I use the function translate(key, lang=lang) and associate an ID in the format {"type": "text", "file": "whatever", "index": "whatever"} for the callback update_translation.

To register a string with an ID that does not match this format, I use the function register. These strings won’t be translated by the callback update_translation.

Hope it’s clear! There may be an easier way, though.

Awesome! I totally like the sidebar design, especially “App Settings” sitting below and able to expand. Are there any hints on how to create a similar sidebar?

PS: as an applied math guy, I also totally like properly formatted floats :slight_smile:

Thanks! Below a sample script that shows how the body is generated:

app.layout = dbc.Container(
    layout(),
    id="body",
    fluid=True,
    style={
        "width": "100%",
        "min-height": "100vh",
        "display": "flex",
        "overflow-y": "hidden",
    },
)

def layout():
    return [
        html.Div(
            menu.layout(),
            className="menu",
            style={"min-width": "310px", "overflow": "auto", "max-height": "100vh"},
        ),
        html.Div(
            html.Div(id="container"),
            style={"flex-grow": "1", "max-height": "100vh"},
        ),
    ]
1 Like