Compare commits

..

4 Commits

Author SHA1 Message Date
6c905dc3ce feat: implement dynamic README loading based on user language
- Updated app.py to load the README content from language-specific markdown files located in the templates directory.
- Added new index.md files for English and Chinese, providing localized information about PsychoScales.Org and its resources.
2025-06-17 08:27:01 +08:00
e46d17eaf2 feat: enhance load_all_scales function for language support
- Updated load_all_scales to accept an optional language parameter, allowing for the loading of scales based on the specified language.
- Modified calls to load_all_scales in various endpoints to pass the user's detected language, ensuring consistent language handling across the application.
2025-06-17 08:23:10 +08:00
27794895c9 feat: implement multilingual template support
- Updated app.py to dynamically load Jinja2 templates based on the detected language.
- Created new template files for English and Chinese, including base, index, list, scale, and result pages.
- Modified template rendering logic to utilize the appropriate language-specific templates based on user preferences.
2025-06-17 08:08:18 +08:00
8f202262ee feat: add language detection middleware for request handling
- Implemented LanguageMiddleware to determine the user's language based on query parameters, Accept-Language header, or IP address.
- Updated the FastAPI app to include the middleware and pass the detected language to templates.
- Modified base.html to dynamically set the HTML language attribute based on the detected language.
2025-06-17 07:48:38 +08:00
13 changed files with 283 additions and 76 deletions

101
app.py
View File

@ -15,10 +15,8 @@ from datetime import datetime, UTC
import csv
from io import StringIO
from typing import Dict, List
app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp
# Initialize GeoIP2 reader
try:
@ -42,8 +40,62 @@ def get_location_from_ip(ip):
print(f"Error getting location for IP {ip}: {e}")
return None
class LanguageMiddleware(BaseHTTPMiddleware):
def __init__(self, app: ASGIApp):
super().__init__(app)
self.default_language = "en" # Default language
self.supported_languages = ["zh", "en"] # Supported languages
async def dispatch(self, request: Request, call_next):
# Get language from query parameter
lang = request.query_params.get("lang")
# If no language in query params, try to get from Accept-Language header
if not lang:
accept_language = request.headers.get("accept-language", "")
if accept_language:
# Parse Accept-Language header and get the first language
lang = accept_language.split(",")[0].split(";")[0].strip()[:2]
# If still no language, try to detect from IP
if not lang and geoip_reader:
try:
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or \
request.headers.get("X-Real-IP", "") or \
request.client.host
response = geoip_reader.city(ip)
country_to_lang = { # Map country to language
"China": "zh",
"Hong Kong": "zh",
"Taiwan": "zh",
"Macau": "zh",
"United States": "en",
"United Kingdom": "en",
}
lang = country_to_lang.get(response.country.name, self.default_language)
except:
lang = self.default_language
# Ensure language is supported
if lang not in self.supported_languages:
lang = self.default_language
# Add language to request state
request.state.language = lang
# Continue processing the request
response = await call_next(request)
return response
app = FastAPI()
app.add_middleware(LanguageMiddleware)
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = {}
for lang in os.listdir("templates"):
templates[lang] = Jinja2Templates(directory="templates/"+lang)
# 加载所有问卷数据
def load_all_scales():
def load_all_scales(lang: str = None):
scales = {}
tags = []
for root, dirs, files in os.walk(os.path.realpath('scales')):
@ -52,39 +104,40 @@ def load_all_scales():
try:
with open(os.path.join(root, filename), 'r', encoding='utf-8') as f:
scale = yaml.safe_load(f)
scale['instructions']=markdown.markdown(scale['instructions'], extensions=['fenced_code','tables','mdx_math'])
scale['descriptions']=markdown.markdown(scale['descriptions'], extensions=['fenced_code','tables','mdx_math'])
scale['abstract']=markdown.markdown(scale['abstract'], extensions=['fenced_code','tables','mdx_math'])
if 'tag' not in scale:
scale['tag']='其他'
if scale['tag'] not in tags:
tags.append(scale['tag'])
scale_id = os.path.splitext(filename)[0] # 使用文件名作为标识
scales[scale_id] = scale
if lang is None or (scale['lang'] and scale['lang'] == lang):
scale['instructions']=markdown.markdown(scale['instructions'], extensions=['fenced_code','tables','mdx_math'])
scale['descriptions']=markdown.markdown(scale['descriptions'], extensions=['fenced_code','tables','mdx_math'])
scale['abstract']=markdown.markdown(scale['abstract'], extensions=['fenced_code','tables','mdx_math'])
if 'tag' not in scale:
scale['tag']='其他'
if scale['tag'] not in tags:
tags.append(scale['tag'])
scale_id = os.path.splitext(filename)[0] # 使用文件名作为标识
scales[scale_id] = scale
except Exception as e:
print(f"Error loading scale {filename}: {e}")
return tags, scales
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
tags, _ = load_all_scales()
tags, _ = load_all_scales(request.state.language)
# 新增读取README.md的逻辑
readme_content = ""
try:
with open("README.md", "r", encoding="utf-8") as f:
with open("templates/"+request.state.language+"/index.md", "r", encoding="utf-8") as f:
readme_content = markdown.markdown(f.read())
except FileNotFoundError:
pass # 如果README不存在则静默失败
return templates.TemplateResponse("index.html", {
return templates[request.state.language].TemplateResponse("index.html", {
"request": request,
"tags": tags,
"readme_content": readme_content # 新增模板变量
"readme_content": readme_content
})
@app.get("/tag/{tag}", response_class=HTMLResponse)
async def list(request: Request, tag: str):
tags, scales = load_all_scales()
return templates.TemplateResponse("list.html", {
tags, scales = load_all_scales(request.state.language)
return templates[request.state.language].TemplateResponse("list.html", {
"request": request,
"tags": tags,
"scales": scales,
@ -93,10 +146,10 @@ async def list(request: Request, tag: str):
@app.get("/scales/{scale_id}", response_class=HTMLResponse)
async def scale(request: Request, scale_id: str):
tags, scales = load_all_scales()
tags, scales = load_all_scales(request.state.language)
scale = scales.get(scale_id)
if scale:
return templates.TemplateResponse("scale.html", {
return templates[request.state.language].TemplateResponse("scale.html", {
"request": request,
"scale_id": scale_id,
"scale": scale,
@ -107,7 +160,7 @@ async def scale(request: Request, scale_id: str):
@app.post("/scales/{scale_id}", response_class=HTMLResponse)
async def result(request: Request, scale_id: str, db: Session = Depends(get_db)):
form_data = await request.form()
tags, scales = load_all_scales()
tags, scales = load_all_scales(request.state.language)
scale = scales.get(scale_id)
if scale:
responses = {}
@ -144,7 +197,7 @@ async def result(request: Request, scale_id: str, db: Session = Depends(get_db))
db.commit()
except Exception as e:
print(e)
return templates.TemplateResponse("result.html", {
return templates[request.state.language].TemplateResponse("result.html", {
"request": request,
"responses": responses,
"average": average,

42
templates/en/base.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="PsychoScales心理学专业量表数据库提供标准化心理测评工具包含人格测试、症状评估等2000+临床心理学量表,支持在线测评及科研数据导出。">
<meta name="keywords" content="心尺, PsychoScales, 心理学量表,专业心理测评,抑郁症测试,焦虑症自评,在线心理测评系统,心理咨询师工具, 心理测验, 报告解读, 测试题">
<meta name="baidu-site-verification" content="codeva-mPOBUr0rLS" />
<link rel="stylesheet" href="/static/styles.css">
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
{% block head_extra %}{% endblock %}
</head>
<body>
<nav>
<div class="nav-left">
<a href="/" class="logo">PsychoScales.Org</a>
</div>
<div class="menu-toggle" onclick="document.querySelector('.nav-links').classList.toggle('active')"></div>
<ul class="nav-links">
<li><a href="https://doc.psychoscales.com/"">Docs</a></li>
{% for tag in tags %}
<li><a href=" /tag/{{ tag }}"">{{ tag }}</a></li>
{% endfor %}
</ul>
</nav>
<main>
{% block main %}{% endblock %}
</main>
<!-- 添加KaTeX支持 -->
<link rel=" stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body, {delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}]});">
</script>
</body>
</html>

13
templates/en/index.html Normal file
View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>PsychoScales.Org</title>
{% endblock %}
{% block main %}
{% if readme_content %}
<div class="readme-section">
{{ readme_content|safe }}
</div>
{% endif %}
{% endblock %}

21
templates/en/index.md Normal file
View File

@ -0,0 +1,21 @@
# PsychoScales.Org
## About Psychoscales.Org
This is an open source scale framework, built on Python.
The scale loading of this project is completely based on YAML, which strikes a balance between easy editing and easy loading.
[Open source address](https://git.mxr612.io/PsychoScales/PsychoScalesOrg)
Contact us: feedback@psychoscales.com
User community: QQ 513869177
## About PsychoScale
PsychoScale is a professional psychology resource platform that aims to provide users with comprehensive psychology measurement tools and assessment resources. We have brought together a variety of psychological measurement tools covering multiple fields such as emotions, personality, cognitive ability, and mental health to help users better understand their own psychological characteristics and conditions.
As a user of PsychoScale, you can quickly obtain detailed reports and analysis of your own psychological characteristics through simple online tests. These measurement tools are based on authoritative psychological theories and research, ensuring the accuracy and reliability of test results.
This website aims to provide free scale sharing and result calculation.
If you need analysis, you can get a comprehensive and in-depth analysis of AI results at [Psychoscales](https://www.psychoscales.com/).

16
templates/en/list.html Normal file
View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>{{ tag }}</title>
{% endblock %}
{% block main %}
<div class="scale-list">
{% for scale_id, scale in scales.items() %}
{% if scale.tag == tag %}
<a class="title" href="/scales/{{ scale_id }}">{{ scale.get('title','Untitled') }}</a>
<p>{{ scale.abstract|safe }}</p>
{% endif %}
{% endfor %}
</div>
{% endblock %}

17
templates/en/result.html Normal file
View File

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>{{scale.title}}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }} </h1>
<ul>
{% for key, value in responses.items() %}
<li>{{ key }}: Got {{ value }} in a {{options[key][0]}} to {{options[key][1]}} scale, with {{ average[key] }} in average</li>
{% endfor %}
</ul>
<div>
{{scale.descriptions|safe}}
</div>
{% endblock %}

24
templates/en/scale.html Normal file
View File

@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>{{ scale.title }}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }}</h1>
<div>
{{ scale.instructions|safe }}
</div>
<form class="scale" action="/scales/{{ scale_id }}" method="post">
{% for id, question in scale['items'].items() %}
<label for="{{ id }}">{{ id }}. {{ question }}</label>
<div class="scale-button">
{% for option, label in scale.options.items() %}
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="Submit">
</form>
{% endblock %}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- <html lang="zh"> -->
<html lang="zh">
<head>
<meta charset="UTF-8">

View File

@ -1,13 +1,13 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>心尺.Org</title>
{% endblock %}
{% block main %}
{% if readme_content %}
<div class="readme-section">
{{ readme_content|safe }}
</div>
{% endif %}
{% extends 'base.html' %}
{% block head_extra %}
<title>心尺.Org</title>
{% endblock %}
{% block main %}
{% if readme_content %}
<div class="readme-section">
{{ readme_content|safe }}
</div>
{% endif %}
{% endblock %}

21
templates/zh/index.md Normal file
View File

@ -0,0 +1,21 @@
# 心尺.Org
## 关于心尺.Org
这个一个开源的量表框架基于Python搭建。
本项目的量表加载完全基于YAML在易于编辑和易于加载中间达到了平衡。
[开源地址](https://git.mxr612.io/PsychoScales/PsychoScalesOrg)
联系我们feedback@psychoscales.com
用户社区QQ 513869177
## 关于心尺
心尺是一个专业的心理学资源平台,旨在为用户提供全面的心理学测量工具和评估资源。我们汇集了各种心理测量工具,涵盖了情绪、人格、认知能力、心理健康等多个领域,帮助用户更好地了解自己的心理特点和状况。
作为心尺的用户,您可以通过简单的在线测试,快速获取关于自己心理特征的详细报告和分析。这些测量工具基于权威的心理学理论和研究,确保了测试结果的准确性和可靠性。
本网站旨在提供免费的量表分享和结果计算。
如果您需要解析,您可以在[心尺](https://www.psychoscales.com/)得到全面、深入的AI结果解析。

View File

@ -1,17 +1,17 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>{{scale.title}}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }} </h1>
<ul>
{% for key, value in responses.items() %}
<li>{{ key }}: 在 {{options[key][0]}} 到 {{options[key][1]}} 的量表中得分 {{ value }},均分 {{ average[key] }}</li>
{% endfor %}
</ul>
<div>
{{scale.descriptions|safe}}
</div>
{% extends 'base.html' %}
{% block head_extra %}
<title>{{scale.title}}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }} </h1>
<ul>
{% for key, value in responses.items() %}
<li>{{ key }}: 在 {{options[key][0]}} 到 {{options[key][1]}} 的量表中得分 {{ value }},均分 {{ average[key] }}</li>
{% endfor %}
</ul>
<div>
{{scale.descriptions|safe}}
</div>
{% endblock %}

View File

@ -1,24 +1,24 @@
{% extends 'base.html' %}
{% block head_extra %}
<title>{{ scale.title }}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }}</h1>
<div>
{{ scale.instructions|safe }}
</div>
<form class="scale" action="/scales/{{ scale_id }}" method="post">
{% for id, question in scale['items'].items() %}
<label for="{{ id }}">{{ id }}. {{ question }}</label>
<div class="scale-button">
{% for option, label in scale.options.items() %}
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="提交">
</form>
{% extends 'base.html' %}
{% block head_extra %}
<title>{{ scale.title }}</title>
{% endblock %}
{% block main %}
<h1>{{ scale.title }}</h1>
<div>
{{ scale.instructions|safe }}
</div>
<form class="scale" action="/scales/{{ scale_id }}" method="post">
{% for id, question in scale['items'].items() %}
<label for="{{ id }}">{{ id }}. {{ question }}</label>
<div class="scale-button">
{% for option, label in scale.options.items() %}
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="提交">
</form>
{% endblock %}