Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
.github
tests
node_modules
npm-debug.log
Dockerfile
I18N.md
README.md
hosted.html
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM nginxinc/nginx-unprivileged:stable-alpine

COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --chown=101:101 index.html downloading upload License.md /usr/share/nginx/html/
COPY --chown=101:101 assets /usr/share/nginx/html/assets

RUN nginx -t

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1:8080/ || exit 1
73 changes: 73 additions & 0 deletions I18N.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Internationalization

This branch adds a small, dependency-free internationalization layer while
keeping English as the source and fallback language.

It is a community-maintained implementation based on the upstream
`openspeedtest/Speed-Test` project; it is not an official OpenSpeedTest release.

## Supported locales

- `en` — English, the default and fallback
- `zh-CN` — Simplified Chinese

If the user has not chosen a language, browsers whose preferred language starts
with `zh` automatically use `zh-CN`; all other browsers use English. A manual
choice is stored in `localStorage` under `openspeedtest.locale`.

## Translation architecture

All user-facing strings live in `assets/js/i18n.js`. Markup references keys
using:

- `data-i18n` for text content
- `data-i18n-title` for HTML `title` attributes
- `data-i18n-aria-label` for accessible names
- `data-i18n-content` for metadata content

The SVG is loaded as an object and then inlined by the upstream application.
`translateSvg()` runs immediately after inlining, so SVG labels and tooltips use
the same dictionary as HTML and runtime status messages.

Runtime messages call `OpenSpeedTestI18n.t("key")` from the readable
`assets/js/app-2.5.4.js`. The original minified upstream bundle remains in the
repository for provenance but is not loaded.

## Adding a locale

1. Add the locale to `SUPPORTED_LOCALES` and `messages` in
`assets/js/i18n.js`.
2. Update `normalizeLocale()` if browser language aliases are needed.
3. Add an option to `#language-select` in `index.html`.
4. Run `npm test`.
5. If Nginx is installed locally, validate the server block with
`nginx -t -p "$PWD/" -c tests/nginx-test.conf`.
6. Test both desktop and portrait layouts in a browser.

Every locale must contain the same keys as English. The tests enforce this and
also fail if HTML, SVG, or runtime code references an unknown translation key.

## Container image

The included Dockerfile is an original, minimal static deployment based
directly on `nginxinc/nginx-unprivileged`. It does not copy files from the
separate OpenSpeedTest Docker-image wrapper.

Build and run:

```sh
docker build -t openspeedtest-i18n:local .
docker run --rm -p 8080:8080 openspeedtest-i18n:local
```

The custom Nginx configuration consumes the upload test's complete POST body
before converting the static-file `405` response to `200`, serves the download
payload without caching, disables access logs, and caps request bodies at 40
MiB. `nginx -t` runs during every image build.

## License

The upstream Speed-Test source is distributed under the MIT License. The
original `License.md` is preserved unchanged and copied into the container
image. New internationalization, tests, and container configuration in this
branch are contributed under the same MIT terms.
70 changes: 58 additions & 12 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ body {
margin: 0px;
padding: 0px;
display: block;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
}

::-webkit-scrollbar {
Expand All @@ -55,7 +56,7 @@ html {
color: rgb(125 119 119);
text-align: center;
font-size:14px;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
}
.Credits a {
Expand All @@ -66,6 +67,51 @@ font-weight: 500;
color: #14b0fe;
}

.language-switcher {
position: fixed;
z-index: 1000;
top: 12px;
right: 12px;
top: max(12px, env(safe-area-inset-top));
right: max(12px, env(safe-area-inset-right));
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
color: #4d4d4d;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(125, 119, 119, 0.25);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
font-size: 12px;
}

.language-switcher select {
max-width: 9rem;
color: inherit;
background: transparent;
border: 0;
font: inherit;
cursor: pointer;
}

.language-switcher select:focus-visible {
outline: 2px solid #14b0fe;
outline-offset: 2px;
}

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.ConnectError {
display: none;

Expand Down Expand Up @@ -108,21 +154,21 @@ color: #14b0fe;
.oDo-Meter {
font-size: 16.633283615112305px;
fill: gray;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
}
.oDoLive-Speed {
font-size: 28px;
fill: #201e1e;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}

.oDoLive-Status {
font-size: 10px;
fill: #d2d1d2;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}
Expand All @@ -146,27 +192,27 @@ color: #14b0fe;
.rtext {
font-size: 12px;
fill: #333;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
}
.rtextnum {
font-size: 23px;
fill: #201e1e;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}
.rtextmbms {
font-size: 12px;
fill: #5f5f5f;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}
.jitter-Mob {
font-size: 9px;
fill: #5f5f5f;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}
Expand All @@ -180,7 +226,7 @@ color: #14b0fe;
.buttonTxt {
font-size: 40px;
fill: #ffffff;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
}
Expand Down Expand Up @@ -241,7 +287,7 @@ color: #14b0fe;
.oDoTop-Speed {
font-size: 16.96px;
fill: gray;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: end;
}
Expand All @@ -265,15 +311,15 @@ color: #14b0fe;
#ipMob {
font-size: 15px;
fill: #201e1e;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
display: none;
}
#ipDesk {
font-size: 15px;
fill: #201e1e;
font-family: Roboto-Medium, Roboto;
font-family: Roboto-Medium, Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
font-weight: 500;
text-anchor: middle;
display: none;
Expand Down
5 changes: 5 additions & 0 deletions assets/css/darkmode.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
body {
background-color: #181818;
}
.language-switcher {
color: #f5f5f5;
background: rgba(24, 24, 24, 0.92);
border-color: rgba(255, 255, 255, 0.2);
}
#ipDesk {
fill: aliceblue;
}
Expand Down
Loading