Skip to content

Commit

Permalink
Merge pull request #1713 from googlefonts/language-spreadsheet
Browse files Browse the repository at this point in the history
Move Fontra UI Strings to a public spreadsheet
  • Loading branch information
justvanrossum authored Oct 13, 2024
2 parents 9e7fbd1 + a93a025 commit a3b6ab1
Show file tree
Hide file tree
Showing 7 changed files with 436 additions and 24 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,10 @@ The following list of features is not complete, but gives you a rough idea of wh

- Serverless Fontra
- Peer-to-peer collaboration

## Translations

We are maintaining various language translations of the UI in a spreadsheet. Please contact us if you'd like to contribute (to) a translation.

- [Translation Documentation](https://docs.google.com/spreadsheets/d/1woTU8dZCHJh7yvdk-N1kgQBUj4Sn3SdRsbKgn6ltJQs/edit?gid=1731105247#gid=1731105247)
- [Fontra UI Strings spreadsheet](https://docs.google.com/spreadsheets/d/1woTU8dZCHJh7yvdk-N1kgQBUj4Sn3SdRsbKgn6ltJQs/edit?usp=sharing)
131 changes: 131 additions & 0 deletions scripts/rebuild_languages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import csv
import io
import json
import os
import pathlib
import subprocess
from urllib.request import urlopen


def prettier(path):
subprocess.run(
[
os.fspath(repoDir / "node_modules" / ".bin" / "prettier"),
os.fspath(path),
"--write",
],
check=True,
)


def jsonDump(s):
return json.dumps(s, ensure_ascii=False)


def downloadSheet(url):
print("downloading", url)
response = urlopen(url)
data = response.read()
file = io.StringIO(data.decode("utf-8"))
reader = csv.reader(file)
return list(reader)


languageSpreadsheetURL = (
"https://docs.google.com/"
"spreadsheets/d/1woTU8dZCHJh7yvdk-N1kgQBUj4Sn3SdRsbKgn6ltJQs/"
"export?format=csv&gid=1383907145"
)

rows = downloadSheet(languageSpreadsheetURL)

numHeaders = 5
headers = rows[:numHeaders]
assert headers[0][0] == "Documentation", headers[0][0]
assert headers[1][2] == "English", headers[1][2]
assert headers[2][2] == "English", headers[2][2]
assert headers[3][2] == "en", headers[3][2]

rows = rows[numHeaders:]

startColumn = 2

languages = []
languageStrings = {}

for columnIndex in range(startColumn, len(headers[1])):
languageInEnglish = headers[1][columnIndex]
languageInLanguage = headers[2][columnIndex]
languageCode = headers[3][columnIndex]
languageStatus = headers[4][columnIndex]
assert languageCode
if not languageStatus.strip():
continue

languages.append(
dict(
code=languageCode,
langEn=languageInEnglish,
langLang=languageInLanguage,
status=languageStatus,
)
)

languageStrings[languageCode] = strings = {}

for row in rows:
key = row[1]
if not key.strip():
continue
string = row[columnIndex]
if not string or string == "-":
string = languageStrings["en"].get(key, "!missing!")
strings[key] = string


repoDir = pathlib.Path(__file__).resolve().parent.parent
langDir = repoDir / "src" / "fontra" / "client" / "lang"
assert langDir.is_dir()
localizationJSPath = repoDir / "src" / "fontra" / "client" / "core" / "localization.js"
assert localizationJSPath.is_file()

localizationJSSource = localizationJSPath.read_text(encoding="utf-8")

languagesList = "\n".join(f" {jsonDump(langs)}," for langs in languages)
languagesBlock = f"""// Don't edit this block, see scripts/rebuild_languages.py
export const languages = [
{languagesList}
];
"""

indexStart = localizationJSSource.find(languagesBlock.splitlines()[0])
assert indexStart > 0
indexEnd = localizationJSSource.find("];\n", indexStart)
assert indexEnd > indexStart

localizationJSSource = (
localizationJSSource[:indexStart]
+ languagesBlock
+ localizationJSSource[indexEnd + 3 :]
)

localizationJSPath.write_text(localizationJSSource, encoding="utf-8")
prettier(localizationJSPath)


languageSourceTemplate = """// Don't edit this file: it is generated by scripts/rebuild_languages.py
export const strings = {{
{stringsBlock}
}};
"""

for languageCode, strings in languageStrings.items():
languagePath = langDir / f"{languageCode.strip()}.js"
print("writing", languagePath)
lines = []
for k, v in sorted(strings.items()):
lines.append(f" {jsonDump(k.strip())}: {jsonDump(v)},")
stringsBlock = "\n".join(lines)
languageSource = languageSourceTemplate.format(stringsBlock=stringsBlock)
languagePath.write_text(languageSource, encoding="utf-8")
prettier(languagePath)
14 changes: 10 additions & 4 deletions src/fontra/client/core/localization.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ObservableController } from "./observable-object.js";
import { fetchJSON } from "./utils.js";

// Don't edit this block, see scripts/rebuild_languages.py
export const languages = [
{ code: "en", langEn: "English", langLang: "English", status: "done" },
{ code: "zh-CN", langEn: "Simplified Chinese", langLang: "简体中文", status: "beta" },
{ code: "nl", langEn: "Dutch", langLang: "Nederlands", status: "wip" },
];

const debugTranslation = false;
let localizationData = {};
Expand All @@ -20,9 +26,9 @@ export const ensureLanguageHasLoaded = new Promise((resolve) => {
function languageChanged(locale) {
// Do explicit .replace() because our cache busting mechanism is simplistic,
// and backtick strings don't work.
const translationsPath = "/lang/locale.json".replace("locale", locale);
fetchJSON(translationsPath).then((data) => {
localizationData = data;
const translationsPath = "/lang/locale.js".replace("locale", locale);
import(translationsPath).then((mod) => {
localizationData = mod.strings;
resolveLanguageHasLoaded();
});
}
Expand Down
19 changes: 12 additions & 7 deletions src/fontra/client/lang/en.json → src/fontra/client/lang/en.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
// Don't edit this file: it is generated by scripts/rebuild_languages.py
export const strings = {
"action-topics.designspace-navigation": "Designspace Navigation",
"action-topics.export-as": "Export as",
"action-topics.glyph-editor-appearance": "Glyph editor appearance",
Expand Down Expand Up @@ -36,6 +37,8 @@
"action.set-contour-start": "Set Start Point",
"action.undo": "Undo",
"application-settings.clipboard.title": "Clipboard",
"application-settings.display-language.status.beta": "beta",
"application-settings.display-language.status.wip": "work in progress",
"application-settings.display-language.title": "Display Language",
"application-settings.editor-behavior.title": "Editor Behavior",
"application-settings.plugins-manager.title": "Plugin Manager",
Expand Down Expand Up @@ -81,9 +84,10 @@
"dialog.add": "Add",
"dialog.cancel": "Cancel",
"dialog.create": "Create",
"dialog.create-new-glyph.body": "Click \"Create\" if you want to create a new glyph named \"%0\"%1.",
"dialog.create-new-glyph.body.2": " for character \"%0\" (%1)",
"dialog.create-new-glyph.title": "Create a new glyph \"%0\"?",
"dialog.create-new-glyph.body":
'Click "Create" if you want to create a new glyph named "%0"%1.',
"dialog.create-new-glyph.body.2": ' for character "%0" (%1)',
"dialog.create-new-glyph.title": 'Create a new glyph "%0"?',
"dialog.glyphs.search": "Search glyphs",
"dialog.replace": "Replace",
"editor.hand-tool": "Hand Tool",
Expand Down Expand Up @@ -155,7 +159,8 @@
"sidebar.reference-font": "Reference Font",
"sidebar.referencefont": "Reference font",
"sidebar.referencefont.customcharacter": "Custom character",
"sidebar.referencefont.info": "Drop one or more .ttf, .otf, .woff or .woff2 files in the field below",
"sidebar.referencefont.info":
"Drop one or more .ttf, .otf, .woff or .woff2 files in the field below",
"sidebar.referencefont.language": "Language",
"sidebar.related-glyphs": "Related Glyphs & Characters",
"sidebar.related-glyphs.title": "Related Glyphs & Characters for %0",
Expand Down Expand Up @@ -239,5 +244,5 @@
"toggle-fullscreen": "Toggle Fullscreen",
"zoom-fit-selection": "Zoom To Fit Selection",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
"zoom-out": "Zoom Out",
};
Loading

0 comments on commit a3b6ab1

Please sign in to comment.