prompt: 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()
  
  ```
  
golden_diff: 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()
  
verification_info: {"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"}]}