We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Feature request] Add server url to "API keys and API docs" section
**Is your feature request related to a problem? Please describe.**
When linking lnbits with external services, (e.g. [zaprite](https://zaprite.com/)) one needs to specify two things: node url and invoice key. 

![image](https://github.com/lnbits/lnbits/assets/19181985/64920942-d120-4d50-951f-99aa8e6b1cca)

Invoice key is clearly visible in the "API keys and API docs" section, but it's sometimes unclear what my "LNbits Node URL" is. 

![image](https://github.com/lnbits/lnbits/assets/19181985/9ae7086b-f48b-4b56-b2aa-6f4a3f42fd96)

**Describe the solution you'd like**
Display "LNbits Node URL" in "Node URL, API keys and docs"

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tools/i18n-ai-tool.py`
Content:
```
1 # 1. Always check the results of the procedure
2 # 2. Always run "npx prettier -w lnbits/static/i18n/XX.js" to reformat the result
3 
4 import os
5 import re
6 import sys
7 
8 import json5
9 from openai import OpenAI
10 
11 if len(sys.argv) < 2:
12     print("Usage: python3 tools/i18n-tool.py <code> [language]")
13     sys.exit(1)
14 lang = sys.argv[1]
15 
16 
17 def load_language(lang):
18     s = open(f"lnbits/static/i18n/{lang}.js", "rt").read()
19     prefix = "window.localisation.%s = {\n" % lang
20     assert s.startswith(prefix)
21     s = s[len(prefix) - 2 :]
22     return json5.loads(s)
23 
24 
25 def save_language(lang, data):
26     with open(f"lnbits/static/i18n/{lang}.js", "wt") as f:
27         f.write("window.localisation.%s = {\n" % lang)
28         row = 0
29         for k, v in data.items():
30             row += 1
31             f.write("  %s:\n" % k)
32             if "'" in v:
33                 f.write('    "%s"' % v)
34             else:
35                 f.write("    '%s'" % v)
36             if row == len(data):
37                 f.write("\n")
38             else:
39                 f.write(",\n")
40         f.write("}\n")
41 
42 
43 def string_variables_match(str1, str2):
44     pat = re.compile(r"%\{[a-z0-9_]*\}")
45     m1 = re.findall(pat, str1)
46     m2 = re.findall(pat, str2)
47     return sorted(m1) == sorted(m2)
48 
49 
50 def translate_string(lang_from, lang_to, text):
51     target = {
52         "de": "German",
53         "es": "Spanish",
54         "jp": "Japan",
55         "cn": "Chinese",
56         "fr": "French",
57         "it": "Italian",
58         "pi": "Pirate",
59         "nl": "Dutch",
60         "we": "Welsh",
61         "pl": "Polish",
62         "pt": "Portuguese",
63         "br": "Brazilian Portugese",
64         "cs": "Czech",
65         "sk": "Slovak",
66         "kr": "Korean",
67     }[lang_to]
68     assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY env var not set"
69     client = OpenAI()
70     try:
71         chat_completion = client.chat.completions.create(
72             messages=[
73                 {
74                     "role": "system",
75                     "content": "You are a language expert that speaks all languages in the world. You are about to translate text from English to another language. The text is a part of the software you are translating. If the given text contains a phrase enclosed by curly preceded with a percent sign, do not translate the given phrase, just keep it verbatim. So for example, the phrase %{amount} translated to target language should still be kept as %{amount}. Never output anything else, just the translated string.",  # noqa: E501
76                 },
77                 {
78                     "role": "user",
79                     "content": f"Translate the following string from English to {target}: {text}",  # noqa: E501
80                 },
81             ],
82             model="gpt-4-1106-preview",  # aka GPT-4 Turbo
83         )
84         translated = chat_completion.choices[0].message.content.strip()
85         # return translated string only if variables were not broken
86         if string_variables_match(text, translated):
87             return translated
88         else:
89             return None
90     except Exception:
91         return None
92 
93 
94 data_en = load_language("en")
95 data = load_language(lang)
96 
97 missing = set(data_en.keys()) - set(data.keys())
98 print(f"Missing {len(missing)} keys in language '{lang}'")
99 
100 if len(missing) > 0:
101     new = {}
102     for k in data_en:
103         if k in data:
104             new[k] = data[k]
105         else:
106             print(f"Translating key '{k}'")
107             print(f"{data_en[k]}")
108             translated = translate_string("en", lang, data_en[k])
109             print("->")
110             if translated:
111                 print(f"{translated}")
112                 new[k] = translated
113             else:
114                 print("ERROR")
115             print()
116     save_language(lang, new)
117 else:
118     # check whether variables match for each string
119     for k in data_en:
120         if not string_variables_match(data_en[k], data[k]):
121             print(f"Variables mismatch ({k}):")
122             print(data_en[k])
123             print(data[k])
124
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/tools/i18n-ai-tool.py b/tools/i18n-ai-tool.py
--- a/tools/i18n-ai-tool.py
+++ b/tools/i18n-ai-tool.py
@@ -64,6 +64,7 @@
         "cs": "Czech",
         "sk": "Slovak",
         "kr": "Korean",
+        "fi": "Finnish",
     }[lang_to]
     assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY env var not set"
     client = OpenAI()

{"golden_diff": "diff --git a/tools/i18n-ai-tool.py b/tools/i18n-ai-tool.py\n--- a/tools/i18n-ai-tool.py\n+++ b/tools/i18n-ai-tool.py\n@@ -64,6 +64,7 @@\n         \"cs\": \"Czech\",\n         \"sk\": \"Slovak\",\n         \"kr\": \"Korean\",\n+        \"fi\": \"Finnish\",\n     }[lang_to]\n     assert os.getenv(\"OPENAI_API_KEY\"), \"OPENAI_API_KEY env var not set\"\n     client = OpenAI()\n", "issue": "[Feature request] Add server url to \"API keys and API docs\" section\n**Is your feature request related to a problem? Please describe.**\r\nWhen linking lnbits with external services, (e.g. [zaprite](https://zaprite.com/)) one needs to specify two things: node url and invoice key. \r\n\r\n![image](https://github.com/lnbits/lnbits/assets/19181985/64920942-d120-4d50-951f-99aa8e6b1cca)\r\n\r\nInvoice key is clearly visible in the \"API keys and API docs\" section, but it's sometimes unclear what my \"LNbits Node URL\" is. \r\n\r\n![image](https://github.com/lnbits/lnbits/assets/19181985/9ae7086b-f48b-4b56-b2aa-6f4a3f42fd96)\r\n\r\n**Describe the solution you'd like**\r\nDisplay \"LNbits Node URL\" in \"Node URL, API keys and docs\"\n", "before_files": [{"content": "# 1. Always check the results of the procedure\n# 2. Always run \"npx prettier -w lnbits/static/i18n/XX.js\" to reformat the result\n\nimport os\nimport re\nimport sys\n\nimport json5\nfrom openai import OpenAI\n\nif len(sys.argv) < 2:\n    print(\"Usage: python3 tools/i18n-tool.py <code> [language]\")\n    sys.exit(1)\nlang = sys.argv[1]\n\n\ndef load_language(lang):\n    s = open(f\"lnbits/static/i18n/{lang}.js\", \"rt\").read()\n    prefix = \"window.localisation.%s = {\\n\" % lang\n    assert s.startswith(prefix)\n    s = s[len(prefix) - 2 :]\n    return json5.loads(s)\n\n\ndef save_language(lang, data):\n    with open(f\"lnbits/static/i18n/{lang}.js\", \"wt\") as f:\n        f.write(\"window.localisation.%s = {\\n\" % lang)\n        row = 0\n        for k, v in data.items():\n            row += 1\n            f.write(\"  %s:\\n\" % k)\n            if \"'\" in v:\n                f.write('    \"%s\"' % v)\n            else:\n                f.write(\"    '%s'\" % v)\n            if row == len(data):\n                f.write(\"\\n\")\n            else:\n                f.write(\",\\n\")\n        f.write(\"}\\n\")\n\n\ndef string_variables_match(str1, str2):\n    pat = re.compile(r\"%\\{[a-z0-9_]*\\}\")\n    m1 = re.findall(pat, str1)\n    m2 = re.findall(pat, str2)\n    return sorted(m1) == sorted(m2)\n\n\ndef translate_string(lang_from, lang_to, text):\n    target = {\n        \"de\": \"German\",\n        \"es\": \"Spanish\",\n        \"jp\": \"Japan\",\n        \"cn\": \"Chinese\",\n        \"fr\": \"French\",\n        \"it\": \"Italian\",\n        \"pi\": \"Pirate\",\n        \"nl\": \"Dutch\",\n        \"we\": \"Welsh\",\n        \"pl\": \"Polish\",\n        \"pt\": \"Portuguese\",\n        \"br\": \"Brazilian Portugese\",\n        \"cs\": \"Czech\",\n        \"sk\": \"Slovak\",\n        \"kr\": \"Korean\",\n    }[lang_to]\n    assert os.getenv(\"OPENAI_API_KEY\"), \"OPENAI_API_KEY env var not set\"\n    client = OpenAI()\n    try:\n        chat_completion = client.chat.completions.create(\n            messages=[\n                {\n                    \"role\": \"system\",\n                    \"content\": \"You are a language expert that speaks all languages in the world. You are about to translate text from English to another language. The text is a part of the software you are translating. If the given text contains a phrase enclosed by curly preceded with a percent sign, do not translate the given phrase, just keep it verbatim. So for example, the phrase %{amount} translated to target language should still be kept as %{amount}. Never output anything else, just the translated string.\",  # noqa: E501\n                },\n                {\n                    \"role\": \"user\",\n                    \"content\": f\"Translate the following string from English to {target}: {text}\",  # noqa: E501\n                },\n            ],\n            model=\"gpt-4-1106-preview\",  # aka GPT-4 Turbo\n        )\n        translated = chat_completion.choices[0].message.content.strip()\n        # return translated string only if variables were not broken\n        if string_variables_match(text, translated):\n            return translated\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndata_en = load_language(\"en\")\ndata = load_language(lang)\n\nmissing = set(data_en.keys()) - set(data.keys())\nprint(f\"Missing {len(missing)} keys in language '{lang}'\")\n\nif len(missing) > 0:\n    new = {}\n    for k in data_en:\n        if k in data:\n            new[k] = data[k]\n        else:\n            print(f\"Translating key '{k}'\")\n            print(f\"{data_en[k]}\")\n            translated = translate_string(\"en\", lang, data_en[k])\n            print(\"->\")\n            if translated:\n                print(f\"{translated}\")\n                new[k] = translated\n            else:\n                print(\"ERROR\")\n            print()\n    save_language(lang, new)\nelse:\n    # check whether variables match for each string\n    for k in data_en:\n        if not string_variables_match(data_en[k], data[k]):\n            print(f\"Variables mismatch ({k}):\")\n            print(data_en[k])\n            print(data[k])\n", "path": "tools/i18n-ai-tool.py"}], "after_files": [{"content": "# 1. Always check the results of the procedure\n# 2. Always run \"npx prettier -w lnbits/static/i18n/XX.js\" to reformat the result\n\nimport os\nimport re\nimport sys\n\nimport json5\nfrom openai import OpenAI\n\nif len(sys.argv) < 2:\n    print(\"Usage: python3 tools/i18n-tool.py <code> [language]\")\n    sys.exit(1)\nlang = sys.argv[1]\n\n\ndef load_language(lang):\n    s = open(f\"lnbits/static/i18n/{lang}.js\", \"rt\").read()\n    prefix = \"window.localisation.%s = {\\n\" % lang\n    assert s.startswith(prefix)\n    s = s[len(prefix) - 2 :]\n    return json5.loads(s)\n\n\ndef save_language(lang, data):\n    with open(f\"lnbits/static/i18n/{lang}.js\", \"wt\") as f:\n        f.write(\"window.localisation.%s = {\\n\" % lang)\n        row = 0\n        for k, v in data.items():\n            row += 1\n            f.write(\"  %s:\\n\" % k)\n            if \"'\" in v:\n                f.write('    \"%s\"' % v)\n            else:\n                f.write(\"    '%s'\" % v)\n            if row == len(data):\n                f.write(\"\\n\")\n            else:\n                f.write(\",\\n\")\n        f.write(\"}\\n\")\n\n\ndef string_variables_match(str1, str2):\n    pat = re.compile(r\"%\\{[a-z0-9_]*\\}\")\n    m1 = re.findall(pat, str1)\n    m2 = re.findall(pat, str2)\n    return sorted(m1) == sorted(m2)\n\n\ndef translate_string(lang_from, lang_to, text):\n    target = {\n        \"de\": \"German\",\n        \"es\": \"Spanish\",\n        \"jp\": \"Japan\",\n        \"cn\": \"Chinese\",\n        \"fr\": \"French\",\n        \"it\": \"Italian\",\n        \"pi\": \"Pirate\",\n        \"nl\": \"Dutch\",\n        \"we\": \"Welsh\",\n        \"pl\": \"Polish\",\n        \"pt\": \"Portuguese\",\n        \"br\": \"Brazilian Portugese\",\n        \"cs\": \"Czech\",\n        \"sk\": \"Slovak\",\n        \"kr\": \"Korean\",\n        \"fi\": \"Finnish\",\n    }[lang_to]\n    assert os.getenv(\"OPENAI_API_KEY\"), \"OPENAI_API_KEY env var not set\"\n    client = OpenAI()\n    try:\n        chat_completion = client.chat.completions.create(\n            messages=[\n                {\n                    \"role\": \"system\",\n                    \"content\": \"You are a language expert that speaks all languages in the world. You are about to translate text from English to another language. The text is a part of the software you are translating. If the given text contains a phrase enclosed by curly preceded with a percent sign, do not translate the given phrase, just keep it verbatim. So for example, the phrase %{amount} translated to target language should still be kept as %{amount}. Never output anything else, just the translated string.\",  # noqa: E501\n                },\n                {\n                    \"role\": \"user\",\n                    \"content\": f\"Translate the following string from English to {target}: {text}\",  # noqa: E501\n                },\n            ],\n            model=\"gpt-4-1106-preview\",  # aka GPT-4 Turbo\n        )\n        translated = chat_completion.choices[0].message.content.strip()\n        # return translated string only if variables were not broken\n        if string_variables_match(text, translated):\n            return translated\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndata_en = load_language(\"en\")\ndata = load_language(lang)\n\nmissing = set(data_en.keys()) - set(data.keys())\nprint(f\"Missing {len(missing)} keys in language '{lang}'\")\n\nif len(missing) > 0:\n    new = {}\n    for k in data_en:\n        if k in data:\n            new[k] = data[k]\n        else:\n            print(f\"Translating key '{k}'\")\n            print(f\"{data_en[k]}\")\n            translated = translate_string(\"en\", lang, data_en[k])\n            print(\"->\")\n            if translated:\n                print(f\"{translated}\")\n                new[k] = translated\n            else:\n                print(\"ERROR\")\n            print()\n    save_language(lang, new)\nelse:\n    # check whether variables match for each string\n    for k in data_en:\n        if not string_variables_match(data_en[k], data[k]):\n            print(f\"Variables mismatch ({k}):\")\n            print(data_en[k])\n            print(data[k])\n", "path": "tools/i18n-ai-tool.py"}]}