Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Internationalization with react-i18next #668

Merged
merged 14 commits into from
Jan 16, 2024
Merged
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
42 changes: 42 additions & 0 deletions backend/chainlit/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import sys
from importlib import util
Expand All @@ -20,6 +21,7 @@

BACKEND_ROOT = os.path.dirname(__file__)
PACKAGE_ROOT = os.path.dirname(os.path.dirname(BACKEND_ROOT))
TRANSLATIONS_DIR = os.path.join(BACKEND_ROOT, "translations")


# Get the directory the script is running from
Expand All @@ -31,6 +33,7 @@

config_dir = os.path.join(APP_ROOT, ".chainlit")
config_file = os.path.join(config_dir, "config.toml")
config_translation_dir = os.path.join(config_dir, "translations")

# Default config file created if none exists
DEFAULT_CONFIG_STR = f"""[project]
Expand Down Expand Up @@ -240,6 +243,28 @@ class ChainlitConfig:
project: ProjectSettings
code: CodeSettings

def load_translation(self, language: str):
translation = {}

translation_lib_file_path = os.path.join(
config_translation_dir, f"{language}.json"
)
default_translation_lib_file_path = os.path.join(
config_translation_dir, f"en-US.json"
)

if os.path.exists(translation_lib_file_path):
with open(translation_lib_file_path, "r", encoding="utf-8") as f:
translation = json.load(f)
elif os.path.exists(default_translation_lib_file_path):
logger.warning(
f"Translation file for {language} not found. Using default translation."
)
with open(default_translation_lib_file_path, "r", encoding="utf-8") as f:
translation = json.load(f)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also handle the case where no translation is found (for instance the user deleted the folder by mistake). We could at least throw an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, we don't have this problem anymore, because the file is created if doesn't exists. But I will insert a warning log to user.


return translation


def init_config(log=False):
"""Initialize the configuration file if it doesn't exist."""
Expand All @@ -251,6 +276,23 @@ def init_config(log=False):
elif log:
logger.info(f"Config file already exists at {config_file}")

if not os.path.exists(config_translation_dir):
os.makedirs(config_translation_dir, exist_ok=True)
logger.info(
f"Created default translation directory at {config_translation_dir}"
)

for file in os.listdir(TRANSLATIONS_DIR):
if file.endswith(".json"):
dst = os.path.join(config_translation_dir, file)
if not os.path.exists(dst):
src = os.path.join(TRANSLATIONS_DIR, file)
with open(src, "r", encoding="utf-8") as f:
translation = json.load(f)
with open(dst, "w", encoding="utf-8") as f:
json.dump(translation, f, indent=4)
logger.info(f"Created default translation file at {dst}")


def load_module(target: str, force_refresh: bool = False):
"""Load the specified module."""
Expand Down
17 changes: 14 additions & 3 deletions backend/chainlit/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,14 @@ async def oauth_login(provider_id: str, request: Request):
url=f"{provider.authorize_url}?{params}",
)
samesite = os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax") # type: Any
secure = samesite.lower() == 'none'
secure = samesite.lower() == "none"
response.set_cookie(
"oauth_state", random, httponly=True, samesite=samesite, secure=secure, max_age=3 * 60
"oauth_state",
random,
httponly=True,
samesite=samesite,
secure=secure,
max_age=3 * 60,
)
return response

Expand Down Expand Up @@ -469,9 +474,14 @@ async def get_providers(

@app.get("/project/settings")
async def project_settings(
current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)]
current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)],
language: str = Query(default="en-US", description="Language code"),
):
"""Return project settings. This is called by the UI before the establishing the websocket connection."""

# Load translation based on the provided language
translation = config.load_translation(language)

profiles = []
if config.code.set_chat_profiles:
chat_profiles = await config.code.set_chat_profiles(current_user)
Expand All @@ -486,6 +496,7 @@ async def project_settings(
"threadResumable": bool(config.code.on_chat_resume),
"markdown": get_markdown_str(config.root),
"chatProfiles": profiles,
"translation": translation,
}
)

Expand Down
158 changes: 158 additions & 0 deletions backend/chainlit/translations/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
{
"components": {
"atoms": {
"buttons": {
"userButton": {
"menu": {
"settings": "Settings",
"settingsKey": "S",
"APIKeys": "API Keys",
"logout": "Logout"
}
}
}
},
"molecules": {
"newChatButton": {
"newChat": "New Chat"
},
"tasklist": {
"TaskList": {
"title": "🗒️ Task List",
"loading": "Loading...",
"error": "An error occured"
}
},
"attachments": {
"cancelUpload": "Cancel upload",
"removeAttachment": "Remove attachment"
},
"newChatDialog": {
"createNewChat": "Create new chat?",
"clearChat": "This will clear the current messages and start a new chat.",
"cancel": "Cancel",
"confirm": "Confirm"
},
"settingsModal": {
"expandMessages": "Expand Messages",
"hideChainOfThought": "Hide Chain of Thought",
"darkMode": "Dark Mode"
}
},
"organisms": {
"chat": {
"history": {
"index": {
"lastInputs": "Last Inputs",
"noInputs": "Such empty...",
"loading": "Loading..."
}
},
"inputBox": {
"input": {
"placeholder": "Type your message here..."
},
"speechButton": {
"start": "Start recording",
"stop": "Stop recording"
},
"SubmitButton": {
"sendMessage": "Send message",
"stopTask": "Stop Task"
},
"UploadButton": {
"attachFiles": "Attach files"
},
"waterMark": {
"text": "Build with"
}
},
"Messages": {
"index": {
"running": "Running",
"executedSuccessfully": "executed successfully",
"failed": "failed",
"feedbackUpdated": "Feedback updated",
"updating": "Updating"
}
},
"dropScreen": {
"dropYourFilesHere": "Drop your files here"
},
"index": {
"failedToUpload": "Failed to upload",
"cancelledUploadOf": "Cancelled upload of",
"couldNotReachServer": "Could not reach the server"
},
"settings": {
"settingsPanel": "Settings panel",
"reset": "Reset",
"cancel": "Cancel",
"confirm": "Confirm"
}
},
"threadHistory": {
"sidebar": {
"filters": {
"FeedbackSelect": {
"feedbackAll": "Feedback: All",
"feedbackPositive": "Feedback: Positive",
"feedbackNegative": "Feedback: Negative"
},
"SearchBar": {
"search": "Search"
}
},
"DeleteThreadButton": {
"confirmMessage": "This will delete the thread as well as it's messages and elements.",
"cancel": "Cancel",
"confirm": "Confirm",
"deletingChat": "Deleting chat",
"chatDeleted": "Chat deleted"
},
"index": {
"pastChats": "Past Chats"
},
"ThreadList": {
"empty": "Empty..."
},
"TriggerButton": {
"closeSidebar": "Close sidebar",
"openSidebar": "Open sidebar"
}
},
"Thread": {
"backToChat": "Go back to chat",
"chatCreatedOn": "This chat was created on"
}
},
"header": {
"chat": "Chat",
"readme": "Readme"
}
}
},
"hooks": {
"useLLMProviders": {
"failedToFetchProviders": "Failed to fetch providers:"
}
},
"pages": {
"Design": {},
"Env": {
"savedSuccessfully": "Saved successfully",
"requiredApiKeys": "Required API Keys",
"requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage."
},
"Login": {
"authTitle": "Login to access the app."
},
"Page": {
"notPartOfProject": "You are not part of this project."
},
"ResumeButton": {
"resumeChat": "Resume Chat"
}
}
}

Loading