Compare commits
12 Commits
aff1586c9b
...
i18n
| Author | SHA1 | Date | |
|---|---|---|---|
| 76143a9c2f | |||
| 1ba9a66c8e | |||
| 14aea2a475 | |||
| d38bb7d986 | |||
| f71dccf166 | |||
| cc73fc4af2 | |||
| 76a5c0b454 | |||
| 2b7a92097c | |||
| c9f8c69e42 | |||
| bc41823dc3 | |||
| 6c2047033b | |||
| 6c3f6c8d03 |
@@ -5,8 +5,9 @@ from uuid import UUID
|
|||||||
|
|
||||||
from beanie import PydanticObjectId
|
from beanie import PydanticObjectId
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from pydantic.json_schema import SkipJsonSchema
|
||||||
|
|
||||||
from firm.core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
|
from firm.core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry, ForeignKey
|
||||||
from firm.core.filter import Filter, FilterSchema
|
from firm.core.filter import Filter, FilterSchema
|
||||||
from firm.entity.models import Entity
|
from firm.entity.models import Entity
|
||||||
|
|
||||||
@@ -25,27 +26,10 @@ class ContractDraftStatus(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class DraftParty(BaseModel):
|
class DraftParty(BaseModel):
|
||||||
entity_id: PydanticObjectId = Field(
|
entity_id: PydanticObjectId = ForeignKey("entities", "Entity", default="", title="Partie")
|
||||||
foreignKey={
|
entity: SkipJsonSchema[Entity] = Field(default=None, exclude=True, )
|
||||||
"reference": {
|
|
||||||
"resource": "entities",
|
|
||||||
"schema": "Entity",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
default="",
|
|
||||||
title="Partie"
|
|
||||||
)
|
|
||||||
part: str = Field(title="Rôle")
|
part: str = Field(title="Rôle")
|
||||||
representative_id: PydanticObjectId = Field(
|
representative_id: PydanticObjectId = ForeignKey("entities", "Entity", default="", title="Représentant")
|
||||||
foreignKey={
|
|
||||||
"reference": {
|
|
||||||
"resource": "entities",
|
|
||||||
"schema": "Entity",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
default="",
|
|
||||||
title="Représentant"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
title = 'Partie'
|
title = 'Partie'
|
||||||
@@ -74,14 +58,10 @@ class ProvisionGenuine(BaseModel):
|
|||||||
|
|
||||||
class ContractProvisionTemplateReference(BaseModel):
|
class ContractProvisionTemplateReference(BaseModel):
|
||||||
type: Literal['template'] = ContractProvisionType.template
|
type: Literal['template'] = ContractProvisionType.template
|
||||||
provision_template_id: PydanticObjectId = Field(
|
provision_template_id: PydanticObjectId = ForeignKey(
|
||||||
foreignKey={
|
"templates/provisions",
|
||||||
"reference": {
|
"ProvisionTemplate",
|
||||||
"resource": "templates/provisions",
|
displayed_fields=['title', 'body'],
|
||||||
"schema": "ProvisionTemplate",
|
|
||||||
"displayedFields": ['title', 'body']
|
|
||||||
},
|
|
||||||
},
|
|
||||||
props={"parametrized": True},
|
props={"parametrized": True},
|
||||||
default="",
|
default="",
|
||||||
title="Template de clause"
|
title="Template de clause"
|
||||||
@@ -173,6 +153,9 @@ class ContractDraft(CrudDocument):
|
|||||||
update = ContractDraftUpdateStatus(status=status)
|
update = ContractDraftUpdateStatus(status=status)
|
||||||
await self.update(db, self, update)
|
await self.update(db, self, update)
|
||||||
|
|
||||||
|
def compute_label(self) -> str:
|
||||||
|
return f"{self.name} - {self.title}"
|
||||||
|
|
||||||
class Contract(CrudDocument):
|
class Contract(CrudDocument):
|
||||||
"""
|
"""
|
||||||
Contrat publié. Les contrats ne peuvent pas être modifiés.
|
Contrat publié. Les contrats ne peuvent pas être modifiés.
|
||||||
|
|||||||
@@ -114,6 +114,20 @@ def RichtextSingleline(*args, **kwargs):
|
|||||||
return Field(*args, **kwargs)
|
return Field(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ForeignKey(resource, schema, displayed_fields=None, *args, **kwargs):
|
||||||
|
kwargs["foreignKey"] = {
|
||||||
|
"reference": {
|
||||||
|
"resource": resource,
|
||||||
|
"schema": schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayed_fields:
|
||||||
|
kwargs["foreignKey"]["reference"]["displayedFields"] = displayed_fields
|
||||||
|
|
||||||
|
return Field(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DictionaryEntry(BaseModel):
|
class DictionaryEntry(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
value: str = ""
|
value: str = ""
|
||||||
|
|||||||
@@ -32,6 +32,19 @@ services:
|
|||||||
- "traefik.http.routers.gui.rule=PathPrefix(`/`)"
|
- "traefik.http.routers.gui.rule=PathPrefix(`/`)"
|
||||||
- "traefik.http.services.gui.loadbalancer.server.port=5173"
|
- "traefik.http.services.gui.loadbalancer.server.port=5173"
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
build:
|
||||||
|
context: ./i18n
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./i18n/app/src:/app/src
|
||||||
|
- ./gui/rpk-gui/public:/app/public
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.i18n.entrypoints=web"
|
||||||
|
- "traefik.http.routers.i18n.rule=PathPrefix(`/locales/add`)"
|
||||||
|
- "traefik.http.services.i18n.loadbalancer.server.port=8100"
|
||||||
|
|
||||||
proxy:
|
proxy:
|
||||||
image: traefik:latest
|
image: traefik:latest
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
164
gui/rpk-gui/public/locales/DE/common.json
Normal file
164
gui/rpk-gui/public/locales/DE/common.json
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"login": {
|
||||||
|
"title": "Melden Sie sich bei Ihrem Konto an",
|
||||||
|
"signin": "Einloggen",
|
||||||
|
"signup": "Anmelden",
|
||||||
|
"divider": "oder",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Passwort"
|
||||||
|
},
|
||||||
|
"oauth": {
|
||||||
|
"google": "Einloggen mit Google",
|
||||||
|
"discord": "Einloggen mit Discord"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Ungültige E-Mail-Adresse",
|
||||||
|
"requiredEmail": "E-Mail ist erforderlich",
|
||||||
|
"requiredPassword": "Passwort wird benötigt"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Anmeldung",
|
||||||
|
"forgotPassword": "Passwort vergessen?",
|
||||||
|
"noAccount": "Sie haben kein Konto?",
|
||||||
|
"rememberMe": "Erinnere dich an mich"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forgotPassword": {
|
||||||
|
"title": "Haben Sie Ihr Passwort vergessen?",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Ungültige E-Mail-Adresse",
|
||||||
|
"requiredEmail": "E-Mail ist erforderlich"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Anweisungen zum Zurücksetzen senden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Registrieren Sie sich für Ihr Konto",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Passwort"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Ungültige E-Mail-Adresse",
|
||||||
|
"requiredEmail": "E-Mail ist erforderlich",
|
||||||
|
"requiredPassword": "Passwort wird benötigt"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Registrieren",
|
||||||
|
"haveAccount": "Ein Konto haben?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updatePassword": {
|
||||||
|
"title": "Kennwort aktualisieren",
|
||||||
|
"fields": {
|
||||||
|
"password": "Neues Passwort",
|
||||||
|
"confirmPassword": "Bestätige neues Passwort"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"confirmPasswordNotMatch": "Passwörter stimmen nicht überein",
|
||||||
|
"requiredPassword": "Passwort wird benötigt",
|
||||||
|
"requiredConfirmPassword": "Das Feld „Passwort bestätigen“ ist erforderlich"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Aktualisieren"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"info": "Sie haben vergessen, {{action}} component zu {{resource}} hinzufügen.",
|
||||||
|
"404": "Leider existiert diese Seite nicht.",
|
||||||
|
"resource404": "Haben Sie die {{resource}} resource erstellt?",
|
||||||
|
"backHome": "Zurück"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"list": "Aufführen",
|
||||||
|
"create": "Erstellen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"show": "Zeigen"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Erstellen",
|
||||||
|
"save": "Speichern",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "Sicher?",
|
||||||
|
"filter": "Filter",
|
||||||
|
"clear": "Löschen",
|
||||||
|
"refresh": "Erneuern",
|
||||||
|
"show": "Zeigen",
|
||||||
|
"undo": "Undo",
|
||||||
|
"import": "Importieren",
|
||||||
|
"clone": "Klon",
|
||||||
|
"notAccessTitle": "Sie haben keine zugriffsberechtigung"
|
||||||
|
},
|
||||||
|
"warnWhenUnsavedChanges": "Nicht gespeicherte Änderungen werden nicht übernommen.",
|
||||||
|
"notifications": {
|
||||||
|
"success": "Erfolg",
|
||||||
|
"error": "Fehler (status code: {{statusCode}})",
|
||||||
|
"undoable": "Sie haben {{seconds}} Sekunden Zeit für Undo.",
|
||||||
|
"createSuccess": "{{resource}} erfolgreich erstellt.",
|
||||||
|
"createError": "Fehler beim Erstellen {{resource}} (status code: {{statusCode}})",
|
||||||
|
"deleteSuccess": "{{resource}} erfolgreich gelöscht.",
|
||||||
|
"deleteError": "Fehler beim Löschen {{resource}} (status code: {{statusCode}})",
|
||||||
|
"editSuccess": "{{resource}} erfolgreich bearbeitet.",
|
||||||
|
"editError": "Fehler beim Bearbeiten {{resource}} (status code: {{statusCode}})",
|
||||||
|
"importProgress": "{{processed}}/{{total}} importiert"
|
||||||
|
},
|
||||||
|
"loading": "Wird geladen",
|
||||||
|
"tags": {
|
||||||
|
"clone": "Klon"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard"
|
||||||
|
},
|
||||||
|
"posts": {
|
||||||
|
"posts": "Einträge",
|
||||||
|
"fields": {
|
||||||
|
"id": "Id",
|
||||||
|
"title": "Titel",
|
||||||
|
"category": "Kategorie",
|
||||||
|
"status": {
|
||||||
|
"title": "Status",
|
||||||
|
"published": "Veröffentlicht",
|
||||||
|
"draft": "Draft",
|
||||||
|
"rejected": "Abgelehnt"
|
||||||
|
},
|
||||||
|
"content": "Inhalh",
|
||||||
|
"createdAt": "Erstellt am"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"create": "Erstellen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"list": "Einträge",
|
||||||
|
"show": "Eintrag zeigen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"actions": "Aktionen"
|
||||||
|
},
|
||||||
|
"documentTitle": {
|
||||||
|
"default": "refine",
|
||||||
|
"suffix": " | Refine",
|
||||||
|
"post": {
|
||||||
|
"list": "Beiträge | Refine",
|
||||||
|
"show": "#{{id}} Beitrag anzeigen | Refine",
|
||||||
|
"edit": "#{{id}} Beitrag bearbeiten | Refine",
|
||||||
|
"create": "Neuen Beitrag erstellen | Refine",
|
||||||
|
"clone": "#{{id}} Beitrag klonen | Refine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoSave": {
|
||||||
|
"success": "gespeichert",
|
||||||
|
"error": "fehler beim automatischen speichern",
|
||||||
|
"loading": "speichern...",
|
||||||
|
"idle": "warten auf anderungen"
|
||||||
|
}
|
||||||
|
}
|
||||||
273
gui/rpk-gui/public/locales/EN/common.json
Normal file
273
gui/rpk-gui/public/locales/EN/common.json
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"home": {
|
||||||
|
"title": "Home"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in to your account",
|
||||||
|
"signin": "Sign in",
|
||||||
|
"signup": "Sign up",
|
||||||
|
"divider": "or",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"oauth": {
|
||||||
|
"google": "Sign in with Google",
|
||||||
|
"discord": "Sign in with Discord"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address",
|
||||||
|
"requiredEmail": "Email is required",
|
||||||
|
"requiredPassword": "Password is required"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Login",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"noAccount": "Don’t have an account?",
|
||||||
|
"rememberMe": "Remember me"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forgotPassword": {
|
||||||
|
"title": "Forgot your password?",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address",
|
||||||
|
"requiredEmail": "Email is required"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Send reset instructions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Sign up for your account",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address",
|
||||||
|
"requiredEmail": "Email is required",
|
||||||
|
"requiredPassword": "Password is required"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Register",
|
||||||
|
"haveAccount": "Have an account?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updatePassword": {
|
||||||
|
"title": "Update password",
|
||||||
|
"fields": {
|
||||||
|
"password": "New Password",
|
||||||
|
"confirmPassword": "Confirm new password"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"confirmPasswordNotMatch": "Passwords do not match",
|
||||||
|
"requiredPassword": "Password required",
|
||||||
|
"requiredConfirmPassword": "Confirm password is required"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Update"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"404": "Sorry, the page you visited does not exist.",
|
||||||
|
"info": "You may have forgotten to add the {{action}} component to {{resource}} resource.",
|
||||||
|
"resource404": "Are you sure you have created the {{resource}} resource.",
|
||||||
|
"backHome": "Back Home"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"list": "List",
|
||||||
|
"create": "Create",
|
||||||
|
"edit": "Edit",
|
||||||
|
"show": "Show"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Create",
|
||||||
|
"save": "Save",
|
||||||
|
"logout": "Logout",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Are you sure?",
|
||||||
|
"filter": "Filter",
|
||||||
|
"clear": "Clear",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"show": "Show",
|
||||||
|
"undo": "Undo",
|
||||||
|
"import": "Import",
|
||||||
|
"clone": "Clone",
|
||||||
|
"notAccessTitle": "You don't have permission to access"
|
||||||
|
},
|
||||||
|
"warnWhenUnsavedChanges": "Are you sure you want to leave? You have unsaved changes.",
|
||||||
|
"notifications": {
|
||||||
|
"success": "Successful",
|
||||||
|
"error": "Error (status code: {{statusCode}})",
|
||||||
|
"undoable": "You have {{seconds}} seconds to undo",
|
||||||
|
"createSuccess": "Successfully created {{resource}}",
|
||||||
|
"createError": "There was an error creating {{resource}} (status code: {{statusCode}})",
|
||||||
|
"deleteSuccess": "Successfully deleted {{resource}}",
|
||||||
|
"deleteError": "Error when deleting {{resource}} (status code: {{statusCode}})",
|
||||||
|
"editSuccess": "Successfully edited {{resource}}",
|
||||||
|
"editError": "Error when editing {{resource}} (status code: {{statusCode}})",
|
||||||
|
"importProgress": "Importing: {{processed}}/{{total}}"
|
||||||
|
},
|
||||||
|
"loading": "Loading",
|
||||||
|
"tags": {
|
||||||
|
"clone": "Clone"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard"
|
||||||
|
},
|
||||||
|
"posts": {
|
||||||
|
"posts": "Posts",
|
||||||
|
"fields": {
|
||||||
|
"id": "Id",
|
||||||
|
"title": "Title",
|
||||||
|
"category": "Category",
|
||||||
|
"status": {
|
||||||
|
"title": "Status",
|
||||||
|
"published": "Published",
|
||||||
|
"draft": "Draft",
|
||||||
|
"rejected": "Rejected"
|
||||||
|
},
|
||||||
|
"content": "Content",
|
||||||
|
"createdAt": "Created At"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"create": "Create Post",
|
||||||
|
"edit": "Edit Post",
|
||||||
|
"list": "Posts",
|
||||||
|
"show": "Show Post"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"documentTitle": {
|
||||||
|
"default": "refine",
|
||||||
|
"suffix": " | Refine",
|
||||||
|
"post": {
|
||||||
|
"list": "Posts | Refine",
|
||||||
|
"show": "#{{id}} Show Post | Refine",
|
||||||
|
"edit": "#{{id}} Edit Post | Refine",
|
||||||
|
"create": "Create new Post | Refine",
|
||||||
|
"clone": "#{{id}} Clone Post | Refine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoSave": {
|
||||||
|
"success": "saved",
|
||||||
|
"error": "auto save failure",
|
||||||
|
"loading": "saving...",
|
||||||
|
"idle": "waiting for changes"
|
||||||
|
},
|
||||||
|
"undefined": {
|
||||||
|
"undefined": "No translation",
|
||||||
|
"titles": {
|
||||||
|
"list": "No translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"individual": {
|
||||||
|
"type": "Individual",
|
||||||
|
"lastname": "Lastname",
|
||||||
|
"surnames": "Surname",
|
||||||
|
"day_of_birth": "Date of birth",
|
||||||
|
"firstname": "Firstname",
|
||||||
|
"place_of_birth": "Place of birth",
|
||||||
|
"middlename": "Middlename",
|
||||||
|
"resource_title": "Individual"
|
||||||
|
},
|
||||||
|
"corporation": {
|
||||||
|
"type": "Corporation",
|
||||||
|
"activity": "Activity",
|
||||||
|
"title": "Title",
|
||||||
|
"employees": "Employees",
|
||||||
|
"resource_title": "Corporation"
|
||||||
|
},
|
||||||
|
"employee": {
|
||||||
|
"position": "Position",
|
||||||
|
"entity_id": "Identity",
|
||||||
|
"resource_title": "Employee"
|
||||||
|
},
|
||||||
|
"institution": {
|
||||||
|
"type": "Institution",
|
||||||
|
"title": "Title",
|
||||||
|
"activity": "Activity",
|
||||||
|
"employees": "Employees",
|
||||||
|
"resource_title": "Institution"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"entity_data": "Informations",
|
||||||
|
"address": "Address",
|
||||||
|
"resource_title": "Entity"
|
||||||
|
},
|
||||||
|
"provision_template": {
|
||||||
|
"name": "Name",
|
||||||
|
"title": "Title",
|
||||||
|
"body": "Body",
|
||||||
|
"resource_title": "Provision Template"
|
||||||
|
},
|
||||||
|
"contract_template": {
|
||||||
|
"name": "Name",
|
||||||
|
"title": "Title",
|
||||||
|
"provisions": "Provisions",
|
||||||
|
"parties": "Parties",
|
||||||
|
"variables": "Variables",
|
||||||
|
"resource_title": "Contract Template"
|
||||||
|
},
|
||||||
|
"party_template": {
|
||||||
|
"entity_id": "Party Template",
|
||||||
|
"representative_id": "Representative",
|
||||||
|
"part": "Part",
|
||||||
|
"resource_title": "Party"
|
||||||
|
},
|
||||||
|
"provision_template_reference": {
|
||||||
|
"provision_template_id": "Provision Template",
|
||||||
|
"resource_title": "Provision Template"
|
||||||
|
},
|
||||||
|
"dictionary_entry": {
|
||||||
|
"key": "Variable",
|
||||||
|
"value": "Value",
|
||||||
|
"resource_title": "Variables"
|
||||||
|
},
|
||||||
|
"contract_draft": {
|
||||||
|
"name": "Name",
|
||||||
|
"title": "Title",
|
||||||
|
"parties": "Parties",
|
||||||
|
"provisions": "Provisions",
|
||||||
|
"variables": "Variables",
|
||||||
|
"resource_title": "Contract Draft"
|
||||||
|
},
|
||||||
|
"draft_party": {
|
||||||
|
"entity_id": "Client",
|
||||||
|
"part": "Part",
|
||||||
|
"representative_id": "Representative",
|
||||||
|
"resource_title": "Party"
|
||||||
|
},
|
||||||
|
"contract_provision_template_reference": {
|
||||||
|
"provision_template_id": "Provision Template",
|
||||||
|
"type": "Provision Template",
|
||||||
|
"resource_title": "Provision Template"
|
||||||
|
},
|
||||||
|
"provision_genuine": {
|
||||||
|
"title": "Title",
|
||||||
|
"body": "Body",
|
||||||
|
"type": "Genuine Provision",
|
||||||
|
"resource_title": "Genuine Provision"
|
||||||
|
},
|
||||||
|
"draft_provision": {
|
||||||
|
"provision": "Provision",
|
||||||
|
"resource_title": "Provision"
|
||||||
|
},
|
||||||
|
"contract": {
|
||||||
|
"date": "Date",
|
||||||
|
"location": "Location",
|
||||||
|
"resource_title": "Contract",
|
||||||
|
"draft_id": "Draft"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
273
gui/rpk-gui/public/locales/FR/common.json
Normal file
273
gui/rpk-gui/public/locales/FR/common.json
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"home": {
|
||||||
|
"title": "Page d'accueil"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Authentification",
|
||||||
|
"signin": "S'authentifier",
|
||||||
|
"signup": "Créer un compte",
|
||||||
|
"divider": "ou",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Mot de passe"
|
||||||
|
},
|
||||||
|
"oauth": {
|
||||||
|
"google": "S'authentifier avec Google",
|
||||||
|
"discord": "S'authentifier avec Discord"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Email invalide",
|
||||||
|
"requiredEmail": "l'Email est obligatoire",
|
||||||
|
"requiredPassword": "Le mot de passe est obligatoire"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "S'authentifier",
|
||||||
|
"forgotPassword": "Mot de passe oublié?",
|
||||||
|
"noAccount": "Vous n'avec pas de compte?",
|
||||||
|
"rememberMe": "Se souvenir de moi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forgotPassword": {
|
||||||
|
"title": "Mot de passe oublié?",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "mail invalide",
|
||||||
|
"requiredEmail": "l'Email est obligatoire"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Envoyer les instructions de récupération"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Création de compte",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Mot de passe"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Email invalide",
|
||||||
|
"requiredEmail": "l'Email est obligatoire",
|
||||||
|
"requiredPassword": "Le mot de passe est obligatoire"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Créer un compte",
|
||||||
|
"haveAccount": "Vous avez déjà un compte?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updatePassword": {
|
||||||
|
"title": "Mise à jour du mot de passe",
|
||||||
|
"fields": {
|
||||||
|
"password": "Nouveau mot de passe",
|
||||||
|
"confirmPassword": "Confirmation"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"confirmPasswordNotMatch": "Les mots de passe ne correspondent pas",
|
||||||
|
"requiredPassword": "Le mot de passe est obligatoire",
|
||||||
|
"requiredConfirmPassword": "Vous devez confirmer votre mot de passe"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Mettre à jour"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"404": "Cette page n'existe pas.",
|
||||||
|
"info": "Il manque l'action {{action}} component à la ressource {{resource}} .",
|
||||||
|
"resource404": "Cette page n'existe pas.",
|
||||||
|
"backHome": "Retour à l'accueil"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"list": "Liste",
|
||||||
|
"create": "Création",
|
||||||
|
"edit": "Édtion",
|
||||||
|
"show": "Voir"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Créer",
|
||||||
|
"save": "Sauvegarder",
|
||||||
|
"logout": "Déconnexion",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"confirm": "Êtes vous sur?",
|
||||||
|
"filter": "Filtrer",
|
||||||
|
"clear": "Effacer",
|
||||||
|
"refresh": "Rafraîchir",
|
||||||
|
"show": "Voir",
|
||||||
|
"undo": "Annuler",
|
||||||
|
"import": "Importer",
|
||||||
|
"clone": "Cloner",
|
||||||
|
"notAccessTitle": "Vous n'avez pas la permission d'accéder à cette ressource"
|
||||||
|
},
|
||||||
|
"warnWhenUnsavedChanges": "Êtes vous sur de vouloir quitter la page? Vous avez des modification non sauvegardées.",
|
||||||
|
"notifications": {
|
||||||
|
"success": "Succès",
|
||||||
|
"error": "Erreur (Code de statut: {{statusCode}})",
|
||||||
|
"undoable": "Vous avez {{seconds}} secondes à annuler",
|
||||||
|
"createSuccess": "Création de {{resource}} réussie",
|
||||||
|
"createError": "Erreur pendant la création de {{resource}} (Code de statut: {{statusCode}})",
|
||||||
|
"deleteSuccess": "Suppression de {{resource}} réussie",
|
||||||
|
"deleteError": "Erreur pendant la suppression de {{resource}} (Code de statut: {{statusCode}})",
|
||||||
|
"editSuccess": "Modification de {{resource}} réussie",
|
||||||
|
"editError": "Erreur pendant la modification de {{resource}} (Code de statut: {{statusCode}})",
|
||||||
|
"importProgress": "Importation de: {{processed}}/{{total}}"
|
||||||
|
},
|
||||||
|
"loading": "Chargement",
|
||||||
|
"tags": {
|
||||||
|
"clone": "Clone"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Tableau de bord"
|
||||||
|
},
|
||||||
|
"posts": {
|
||||||
|
"posts": "Posts",
|
||||||
|
"fields": {
|
||||||
|
"id": "Id",
|
||||||
|
"title": "Title",
|
||||||
|
"category": "Category",
|
||||||
|
"status": {
|
||||||
|
"title": "Status",
|
||||||
|
"published": "Published",
|
||||||
|
"draft": "Draft",
|
||||||
|
"rejected": "Rejected"
|
||||||
|
},
|
||||||
|
"content": "Content",
|
||||||
|
"createdAt": "Created At"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"create": "Create Post",
|
||||||
|
"edit": "Edit Post",
|
||||||
|
"list": "Posts",
|
||||||
|
"show": "Show Post"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"documentTitle": {
|
||||||
|
"default": "refine",
|
||||||
|
"suffix": " | Refine",
|
||||||
|
"post": {
|
||||||
|
"list": "Posts | Refine",
|
||||||
|
"show": "#{{id}} Show Post | Refine",
|
||||||
|
"edit": "#{{id}} Edit Post | Refine",
|
||||||
|
"create": "Create new Post | Refine",
|
||||||
|
"clone": "#{{id}} Clone Post | Refine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoSave": {
|
||||||
|
"success": "Sauvegardé",
|
||||||
|
"error": "Sauvegarde automatique ratée",
|
||||||
|
"loading": "Sauvegarde...",
|
||||||
|
"idle": "En attente de modification"
|
||||||
|
},
|
||||||
|
"undefined": {
|
||||||
|
"undefined": "No translation",
|
||||||
|
"titles": {
|
||||||
|
"list": "No translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"individual": {
|
||||||
|
"type": "Particulier",
|
||||||
|
"middlename": "Autres prénoms",
|
||||||
|
"lastname": "Nom",
|
||||||
|
"firstname": "Prénom",
|
||||||
|
"day_of_birth": "Date de naissance",
|
||||||
|
"surnames": "Surnoms",
|
||||||
|
"place_of_birth": "Lieu de naissance",
|
||||||
|
"resource_title": "Particulier"
|
||||||
|
},
|
||||||
|
"corporation": {
|
||||||
|
"type": "Entreprise",
|
||||||
|
"title": "Titre",
|
||||||
|
"activity": "Activité",
|
||||||
|
"employees": "Employés",
|
||||||
|
"resource_title": "Entreprise"
|
||||||
|
},
|
||||||
|
"employee": {
|
||||||
|
"entity_id": "Identité",
|
||||||
|
"position": "Poste",
|
||||||
|
"resource_title": "Employé"
|
||||||
|
},
|
||||||
|
"institution": {
|
||||||
|
"type": "Institution",
|
||||||
|
"title": "Titre",
|
||||||
|
"employees": "Employés",
|
||||||
|
"activity": "Activité",
|
||||||
|
"resource_title": "Institution"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"entity_data": "Informations",
|
||||||
|
"address": "Adresse",
|
||||||
|
"resource_title": "Entité"
|
||||||
|
},
|
||||||
|
"provision_template": {
|
||||||
|
"name": "Nom",
|
||||||
|
"body": "Corps",
|
||||||
|
"title": "Titre",
|
||||||
|
"resource_title": "Template de Clause"
|
||||||
|
},
|
||||||
|
"contract_template": {
|
||||||
|
"name": "Nom",
|
||||||
|
"title": "Titre",
|
||||||
|
"parties": "Parties",
|
||||||
|
"provisions": "Clauses",
|
||||||
|
"variables": "Variables",
|
||||||
|
"resource_title": "Template de Contrat"
|
||||||
|
},
|
||||||
|
"party_template": {
|
||||||
|
"entity_id": "Entité",
|
||||||
|
"part": "Rôle",
|
||||||
|
"representative_id": "Représentant",
|
||||||
|
"resource_title": "Partie"
|
||||||
|
},
|
||||||
|
"provision_template_reference": {
|
||||||
|
"provision_template_id": "Template de clause",
|
||||||
|
"resource_title": "Template de clause"
|
||||||
|
},
|
||||||
|
"dictionary_entry": {
|
||||||
|
"key": "Variable",
|
||||||
|
"value": "Valeur",
|
||||||
|
"resource_title": "Variables"
|
||||||
|
},
|
||||||
|
"contract_draft": {
|
||||||
|
"name": "Nom",
|
||||||
|
"parties": "Parties",
|
||||||
|
"title": "Titre",
|
||||||
|
"provisions": "Clauses",
|
||||||
|
"variables": "Variables",
|
||||||
|
"resource_title": "Brouillon de Contrat"
|
||||||
|
},
|
||||||
|
"draft_party": {
|
||||||
|
"part": "Rôle",
|
||||||
|
"representative_id": "Représentant",
|
||||||
|
"entity_id": "Entité",
|
||||||
|
"resource_title": "Partie"
|
||||||
|
},
|
||||||
|
"contract_provision_template_reference": {
|
||||||
|
"type": "Template",
|
||||||
|
"provision_template_id": "Template de clause",
|
||||||
|
"resource_title": "Template de clause"
|
||||||
|
},
|
||||||
|
"provision_genuine": {
|
||||||
|
"type": "Personalisée",
|
||||||
|
"title": "Titre",
|
||||||
|
"body": "Corps",
|
||||||
|
"resource_title": "Clause personnalisée"
|
||||||
|
},
|
||||||
|
"draft_provision": {
|
||||||
|
"provision": "Clause",
|
||||||
|
"resource_title": "Clause"
|
||||||
|
},
|
||||||
|
"contract": {
|
||||||
|
"draft_id": "Brouillon",
|
||||||
|
"resource_title": "Contrat",
|
||||||
|
"location": "Lieu",
|
||||||
|
"date": "Date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"pages": {
|
|
||||||
"login": {
|
|
||||||
"title": "Sign in to your account"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"pages": {
|
|
||||||
"login": {
|
|
||||||
"title": "S'authentifier"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,6 @@ import { FirmRoutes } from "./pages/firm";
|
|||||||
import rpcTheme from "./theme";
|
import rpcTheme from "./theme";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
const i18nProvider: I18nProvider = {
|
const i18nProvider: I18nProvider = {
|
||||||
@@ -78,7 +77,7 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
element={(
|
element={(
|
||||||
<Authenticated key="authenticated-routes" redirectOnFail="/auth/login" fallback={<CatchAllNavigate to="/auth/login"/>}>
|
<Authenticated key="authenticated-routes" redirectOnFail="/login" fallback={<CatchAllNavigate to="/login"/>}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Authenticated>
|
</Authenticated>
|
||||||
)}
|
)}
|
||||||
@@ -86,13 +85,13 @@ function App() {
|
|||||||
<Route path="hub/*" element={<HubRoutes />} />
|
<Route path="hub/*" element={<HubRoutes />} />
|
||||||
<Route path="firm/*" element={<FirmRoutes />} />
|
<Route path="firm/*" element={<FirmRoutes />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="auth/*" element={<Outlet />}>
|
<Route path="*" element={<Outlet />}>
|
||||||
<Route path="login" element={<Login />} />
|
<Route path="login" element={<Login />} />
|
||||||
<Route path="register" element={<Register />} />
|
<Route path="register" element={<Register />} />
|
||||||
<Route path="forgot-password" element={<ForgotPassword />} />
|
<Route path="forgot-password" element={<ForgotPassword />} />
|
||||||
<Route path="update-password" element={<UpdatePassword />} />
|
<Route path="update-password" element={<UpdatePassword />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route index element={<><Header /><h1>HOME</h1></>} />
|
<Route index element={<><Header /><h1>{t("pages.home.title")}</h1></>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<UnsavedChangesNotifier />
|
<UnsavedChangesNotifier />
|
||||||
<DocumentTitleHandler />
|
<DocumentTitleHandler />
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
|
import { useSearchParams, Navigate } from "react-router";
|
||||||
|
import { useTranslation } from "@refinedev/core";
|
||||||
import { AuthPage } from "@refinedev/mui";
|
import { AuthPage } from "@refinedev/mui";
|
||||||
|
|
||||||
import GoogleIcon from "@mui/icons-material/Google";
|
import GoogleIcon from "@mui/icons-material/Google";
|
||||||
import DiscordIcon from "../../components/DiscordIcon";
|
import DiscordIcon from "../../components/DiscordIcon";
|
||||||
import {useSearchParams, Navigate, Link} from "react-router";
|
|
||||||
import MuiLink from "@mui/material/Link";
|
|
||||||
import * as React from "react";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
|
|
||||||
export const Login = () => {
|
export const Login = () => {
|
||||||
|
const { translate } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
if (searchParams.get("oauth") == "success") {
|
if (searchParams.get("oauth") == "success") {
|
||||||
const redirect_to = localStorage.getItem("redirect_after_login")
|
const redirect_to = localStorage.getItem("redirect_after_login")
|
||||||
@@ -24,67 +20,15 @@ export const Login = () => {
|
|||||||
rememberMe={false}
|
rememberMe={false}
|
||||||
providers={[{
|
providers={[{
|
||||||
name: "google",
|
name: "google",
|
||||||
label: "Sign in with Google",
|
label: translate("pages.login.oauth.google"),
|
||||||
icon: (<GoogleIcon style={{ fontSize: 24, }} />),
|
icon: (<GoogleIcon style={{ fontSize: 24, }} />),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "discord",
|
name: "discord",
|
||||||
label: "Sign in with Discord",
|
label: translate("pages.login.oauth.discord"),
|
||||||
icon: (<DiscordIcon style={{ fontSize: 24, }} />),
|
icon: (<DiscordIcon style={{ fontSize: 24, }} />),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
forgotPasswordLink={
|
|
||||||
<Stack
|
|
||||||
sx={{
|
|
||||||
direction: "row",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MuiLink
|
|
||||||
variant="body2"
|
|
||||||
color="primary"
|
|
||||||
fontSize="12px"
|
|
||||||
component={Link}
|
|
||||||
underline="none"
|
|
||||||
to="/auth/forgot-password"
|
|
||||||
>
|
|
||||||
Forgot password?
|
|
||||||
</MuiLink>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
registerLink={
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
mt: "24px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
textAlign="center"
|
|
||||||
variant="body2"
|
|
||||||
component="span"
|
|
||||||
fontSize="12px"
|
|
||||||
>
|
|
||||||
Don’t have an account?
|
|
||||||
</Typography>
|
|
||||||
<MuiLink
|
|
||||||
ml="4px"
|
|
||||||
fontSize="12px"
|
|
||||||
variant="body2"
|
|
||||||
color="primary"
|
|
||||||
component={Link}
|
|
||||||
underline="none"
|
|
||||||
to="/auth/register"
|
|
||||||
fontWeight="bold"
|
|
||||||
>
|
|
||||||
Sign up
|
|
||||||
</MuiLink>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import { useLogout } from "@refinedev/core";
|
import { useLogout } from "@refinedev/core";
|
||||||
|
import { useTranslation } from "@refinedev/core";
|
||||||
|
|
||||||
export const Logout = () => {
|
export const Logout = () => {
|
||||||
|
const { translate } = useTranslation();
|
||||||
const { mutate: logout } = useLogout();
|
const { mutate: logout } = useLogout();
|
||||||
|
|
||||||
return <Button onClick={() => logout()} >Logout</Button>;
|
return <Button onClick={() => logout()} >{ translate("buttons.logout") }</Button>;
|
||||||
};
|
};
|
||||||
|
|||||||
41
gui/rpk-gui/src/components/header/I18nPicker.tsx
Normal file
41
gui/rpk-gui/src/components/header/I18nPicker.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useTranslation as useRefineTranslation } from "@refinedev/core";
|
||||||
|
|
||||||
|
const I18nPicker = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const { getLocale, changeLocale } = useRefineTranslation();
|
||||||
|
const currentLocale = getLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
value={currentLocale}
|
||||||
|
options={i18n.languages}
|
||||||
|
disableClearable={true}
|
||||||
|
renderInput={(params) => {
|
||||||
|
return <TextField {...params} label={ "Language" } variant="outlined" />
|
||||||
|
}}
|
||||||
|
renderOption={(props, option) => {
|
||||||
|
const { key, ...optionProps } = props;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={key}
|
||||||
|
component="li"
|
||||||
|
sx={{ '& > img': { mr: 2, flexShrink: 0 } }}
|
||||||
|
{...optionProps}
|
||||||
|
>
|
||||||
|
{ option }
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
changeLocale(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default I18nPicker;
|
||||||
@@ -18,17 +18,11 @@ import { FirmContext } from "../../contexts/FirmContext";
|
|||||||
import { Logout } from "../auth/Logout";
|
import { Logout } from "../auth/Logout";
|
||||||
import { IUser } from "../../interfaces";
|
import { IUser } from "../../interfaces";
|
||||||
import MuiLink from "@mui/material/Link";
|
import MuiLink from "@mui/material/Link";
|
||||||
import { useTranslation } from "react-i18next";
|
import I18nPicker from "./I18nPicker";
|
||||||
import { useTranslation as useTranslationR } from "@refinedev/core";
|
|
||||||
import { useSetLocale } from "@refinedev/core";
|
|
||||||
|
|
||||||
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||||
sticky = true,
|
sticky = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n } = useTranslation();
|
|
||||||
const { getLocale, changeLocale } = useTranslationR();
|
|
||||||
const currentLocale = getLocale();
|
|
||||||
|
|
||||||
const collapsed = false;
|
const collapsed = false;
|
||||||
const { mode, setMode } = useContext(ColorModeContext);
|
const { mode, setMode } = useContext(ColorModeContext);
|
||||||
const { currentFirm } = useContext(FirmContext);
|
const { currentFirm } = useContext(FirmContext);
|
||||||
@@ -44,16 +38,6 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
|||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [anchorIn, setAnchorIn] = React.useState<null | HTMLElement>(null);
|
|
||||||
const openI18nMenu = Boolean(anchorEl);
|
|
||||||
const handleOpenI18nMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
setAnchorIn(event.currentTarget);
|
|
||||||
}
|
|
||||||
const handleCloseI18nMenu = () => {
|
|
||||||
setAnchorIn(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position={sticky ? "sticky" : "relative"}>
|
<AppBar position={sticky ? "sticky" : "relative"}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
@@ -147,45 +131,9 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{!user && (
|
{!user && (
|
||||||
<Link to="/auth/login"><Button>Login</Button></Link>
|
<Link to="/login"><Button>Login</Button></Link>
|
||||||
)}
|
)}
|
||||||
<Button
|
<I18nPicker />
|
||||||
id="i18n-button"
|
|
||||||
aria-controls={openI18nMenu ? 'i18n-menu' : undefined}
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded={openI18nMenu ? 'true' : undefined}
|
|
||||||
onClick={handleOpenI18nMenu}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
display: {
|
|
||||||
xs: "none",
|
|
||||||
sm: "inline-block",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{currentLocale}
|
|
||||||
</Typography>
|
|
||||||
<Avatar src={`/images/flags/${currentLocale}.svg`} alt={currentLocale}/>
|
|
||||||
</Button>
|
|
||||||
<Menu
|
|
||||||
id="i18n-menu"
|
|
||||||
open={openI18nMenu}
|
|
||||||
anchorEl={anchorIn}
|
|
||||||
onClose={handleCloseI18nMenu}
|
|
||||||
>
|
|
||||||
{[...(i18n.languages || [])].sort().map((lang: string) => (
|
|
||||||
<MenuItem
|
|
||||||
key={lang}
|
|
||||||
onClick={() => changeLocale(lang)}
|
|
||||||
>
|
|
||||||
<span style={{ marginRight: 8 }}>
|
|
||||||
<Avatar src={`/images/flags/${lang}.svg`} alt={lang}/>
|
|
||||||
</span>
|
|
||||||
{lang === "en" ? "English" : "Français"}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ i18n
|
|||||||
.use(detector)
|
.use(detector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
supportedLngs: ["en", "fr"],
|
supportedLngs: ["EN", "FR"],
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: "/locales/{{lng}}/{{ns}}.json", // locale files path
|
loadPath: "/locales/{{lng}}/{{ns}}.json", // "http/locales/{{lng}}/{{ns}}.json"
|
||||||
},
|
},
|
||||||
|
//saveMissing: true,
|
||||||
ns: ["common"],
|
ns: ["common"],
|
||||||
defaultNS: "common",
|
defaultNS: "common",
|
||||||
fallbackLng: ["en", "fr"],
|
fallbackLng: ["EN", "FR"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { RegistryFieldsType, RegistryWidgetsType, RJSFSchema, UiSchema } from "@
|
|||||||
import CrudTextWidget from "./widgets/crud-text-widget";
|
import CrudTextWidget from "./widgets/crud-text-widget";
|
||||||
import UnionEnumField from "./fields/union-enum";
|
import UnionEnumField from "./fields/union-enum";
|
||||||
import { ResourceContext } from "../contexts/ResourceContext";
|
import { ResourceContext } from "../contexts/ResourceContext";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type BaseFormProps = {
|
type BaseFormProps = {
|
||||||
schema: RJSFSchema,
|
schema: RJSFSchema,
|
||||||
@@ -12,6 +13,7 @@ type BaseFormProps = {
|
|||||||
onChange?: (data: any) => void,
|
onChange?: (data: any) => void,
|
||||||
uiSchema?: UiSchema,
|
uiSchema?: UiSchema,
|
||||||
formData?: any,
|
formData?: any,
|
||||||
|
children?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const customWidgets: RegistryWidgetsType = {
|
export const customWidgets: RegistryWidgetsType = {
|
||||||
@@ -23,7 +25,7 @@ export const customFields: RegistryFieldsType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BaseForm: React.FC<BaseFormProps> = (props) => {
|
export const BaseForm: React.FC<BaseFormProps> = (props) => {
|
||||||
const { schema, uiSchema, resourceBasePath, formData, onSubmit, onChange } = props;
|
const { schema, uiSchema, resourceBasePath, formData, children, onSubmit, onChange } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourceContext.Provider value={{basePath: resourceBasePath}} >
|
<ResourceContext.Provider value={{basePath: resourceBasePath}} >
|
||||||
@@ -37,6 +39,7 @@ export const BaseForm: React.FC<BaseFormProps> = (props) => {
|
|||||||
widgets={customWidgets}
|
widgets={customWidgets}
|
||||||
fields={customFields}
|
fields={customFields}
|
||||||
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
|
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
|
||||||
|
children={children}
|
||||||
/>
|
/>
|
||||||
</ResourceContext.Provider>
|
</ResourceContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
import { useForm } from "@refinedev/core";
|
import { useForm } from "@refinedev/core";
|
||||||
import { UiSchema } from "@rjsf/utils";
|
import { UiSchema } from "@rjsf/utils";
|
||||||
@@ -12,11 +12,12 @@ type CrudFormProps = {
|
|||||||
resource: string,
|
resource: string,
|
||||||
id?: string,
|
id?: string,
|
||||||
onSuccess?: (data: any) => void,
|
onSuccess?: (data: any) => void,
|
||||||
defaultValue?: any
|
defaultValue?: any,
|
||||||
|
children?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CrudForm: React.FC<CrudFormProps> = (props) => {
|
export const CrudForm: React.FC<CrudFormProps> = (props) => {
|
||||||
const { schemaName, uiSchema, resourceBasePath="" ,resource, id, onSuccess, defaultValue } = props;
|
const { schemaName, uiSchema, resourceBasePath="" ,resource, id, onSuccess, defaultValue, children } = props;
|
||||||
|
|
||||||
const { onFinish, query, formLoading } = useForm({
|
const { onFinish, query, formLoading } = useForm({
|
||||||
resource: resourceBasePath == "" ? resource : `${resourceBasePath}/${resource}`,
|
resource: resourceBasePath == "" ? resource : `${resourceBasePath}/${resource}`,
|
||||||
@@ -57,6 +58,7 @@ export const CrudForm: React.FC<CrudFormProps> = (props) => {
|
|||||||
onSubmit={
|
onSubmit={
|
||||||
(data: any) => onFinish(data)
|
(data: any) => onFinish(data)
|
||||||
}
|
}
|
||||||
|
children={children}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { JSONSchema7Definition } from "json-schema";
|
|
||||||
import { RJSFSchema } from '@rjsf/utils';
|
import { RJSFSchema } from '@rjsf/utils';
|
||||||
|
import i18n from '../../../i18n'
|
||||||
|
|
||||||
const API_URL = "/api/v1";
|
const API_URL = "/api/v1";
|
||||||
|
|
||||||
@@ -19,9 +19,14 @@ const getJsonschema = async (): Promise<RJSFSchema> => {
|
|||||||
return rawSchema;
|
return rawSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertCamelToSnake(str: string): string {
|
||||||
|
return str.replace(/([a-zA-Z])(?=[A-Z])/g,'$1_').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
|
function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
|
||||||
let resource;
|
let resource;
|
||||||
|
|
||||||
|
const shortResourceName = convertCamelToSnake(resourceName.replace(/(-Input|Create|Update)$/g, ""));
|
||||||
resource = structuredClone(rawSchemas.components.schemas[resourceName]);
|
resource = structuredClone(rawSchemas.components.schemas[resourceName]);
|
||||||
resource.components = { schemas: {} };
|
resource.components = { schemas: {} };
|
||||||
for (let prop_name in resource.properties) {
|
for (let prop_name in resource.properties) {
|
||||||
@@ -45,6 +50,13 @@ function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
|
|||||||
} else if (is_array(prop) && is_reference(prop.items)) {
|
} else if (is_array(prop) && is_reference(prop.items)) {
|
||||||
resolveReference(rawSchemas, resource, prop.items);
|
resolveReference(rawSchemas, resource, prop.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prop.hasOwnProperty("title")) {
|
||||||
|
prop.title = i18n.t(`schemas.${shortResourceName}.${convertCamelToSnake(prop_name)}`, prop.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resource.hasOwnProperty("title")) {
|
||||||
|
resource.title = i18n.t(`schemas.${shortResourceName}.resource_title`, resource.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
@@ -161,7 +173,6 @@ function path_exists(rawSchemas: RJSFSchema, resource: RJSFSchema, path: string)
|
|||||||
return has_descendant(rawSchemas, resource, path);
|
return has_descendant(rawSchemas, resource, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return has_descendant(rawSchemas, resource, path.substring(0, pointFirstPosition))
|
return has_descendant(rawSchemas, resource, path.substring(0, pointFirstPosition))
|
||||||
&& path_exists(
|
&& path_exists(
|
||||||
rawSchemas,
|
rawSchemas,
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Route, Routes } from "react-router";
|
import { Route, Routes } from "react-router";
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { useOne } from "@refinedev/core";
|
||||||
|
import { BaseForm } from "../../lib/crud/components/base-form";
|
||||||
|
import { ForeignKeyReference, ForeignKeySchema } from "../../lib/crud/components/widgets/foreign-key";
|
||||||
|
|
||||||
|
import { FirmContext } from "../../contexts/FirmContext";
|
||||||
import List from "./base-page/List";
|
import List from "./base-page/List";
|
||||||
import Edit from "./base-page/Edit";
|
import Edit from "./base-page/Edit";
|
||||||
import New from "./base-page/New";
|
import New from "./base-page/New";
|
||||||
@@ -29,6 +36,79 @@ const EditDraft = () => {
|
|||||||
return <Edit<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
|
return <Edit<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateDraft = () => {
|
type ForeignKeySubSchema = ForeignKeySchema & {
|
||||||
return <New<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
|
properties: { [key: string]: { foreignKey: { reference: ForeignKeyReference } } }
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateDraft = () => {
|
||||||
|
const [chosenDraft, setChosenDraft] = useState<string|null>(null)
|
||||||
|
const { currentFirm } = useContext(FirmContext);
|
||||||
|
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
|
||||||
|
const templateFieldSchema: ForeignKeySubSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
template_id: {
|
||||||
|
type: "string",
|
||||||
|
title: "Find a template",
|
||||||
|
foreignKey: {
|
||||||
|
reference: {
|
||||||
|
resource: "templates/contracts",
|
||||||
|
schema: "ContractTemplate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const templateForm = (
|
||||||
|
<BaseForm
|
||||||
|
schema={templateFieldSchema}
|
||||||
|
formData={{template_id: chosenDraft}}
|
||||||
|
resourceBasePath={resourceBasePath}
|
||||||
|
onChange={(data) => {
|
||||||
|
const { template_id } = data;
|
||||||
|
setChosenDraft(template_id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
</BaseForm>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (chosenDraft !== null) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{templateForm}
|
||||||
|
<CreateDraftFromTemplate template_id={chosenDraft}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{templateForm}
|
||||||
|
<New<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateDraftFromTemplate = (props: { template_id: string }) => {
|
||||||
|
const { template_id } = props;
|
||||||
|
const { currentFirm } = useContext(FirmContext);
|
||||||
|
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
|
||||||
|
const resource = "templates/contracts"
|
||||||
|
const { data, isLoading } = useOne({
|
||||||
|
resource: `${resourceBasePath}/${resource}`,
|
||||||
|
id: template_id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading || data === undefined) {
|
||||||
|
return <CircularProgress />
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = { ...data.data };
|
||||||
|
template.provisions = data.data.provisions.map((item: any) => {
|
||||||
|
return { provision: {type: "template", provision_template_id: item.provision_template_id} }
|
||||||
|
})
|
||||||
|
|
||||||
|
return <New<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} defaultValue={ template }/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { CrudForm } from "../../../lib/crud/components/crud-form";
|
|
||||||
import { UiSchema } from "@rjsf/utils";
|
import { UiSchema } from "@rjsf/utils";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
import { Button } from "@mui/material";
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
import { FirmContext } from "../../../contexts/FirmContext";
|
import { FirmContext } from "../../../contexts/FirmContext";
|
||||||
|
import { CrudForm } from "../../../lib/crud/components/crud-form";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import { DeleteButton } from "@refinedev/mui";
|
||||||
|
|
||||||
type EditProps = {
|
type EditProps = {
|
||||||
resource: string,
|
resource: string,
|
||||||
@@ -17,13 +22,26 @@ const Edit = <T,>(props: EditProps) => {
|
|||||||
const { record_id } = useParams();
|
const { record_id } = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CrudForm
|
<>
|
||||||
schemaName={schemaName}
|
<CrudForm
|
||||||
uiSchema={uiSchema}
|
schemaName={schemaName}
|
||||||
resourceBasePath={resourceBasePath}
|
uiSchema={uiSchema}
|
||||||
resource={resource}
|
resourceBasePath={resourceBasePath}
|
||||||
id={record_id}
|
resource={resource}
|
||||||
/>
|
id={record_id}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}>
|
||||||
|
<Button type='submit' variant="contained" size="large"><SaveIcon />Save</Button>
|
||||||
|
<DeleteButton variant="contained" size="large" color="error" recordItemId={record_id}/>
|
||||||
|
</Stack>
|
||||||
|
</CrudForm>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const authProvider: AuthProvider = {
|
|||||||
if (get_user() == null) {
|
if (get_user() == null) {
|
||||||
return {
|
return {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
redirectTo: "/auth/login",
|
redirectTo: "/login",
|
||||||
logout: true
|
logout: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ const authProvider: AuthProvider = {
|
|||||||
if (error?.status === 401) {
|
if (error?.status === 401) {
|
||||||
forget_user();
|
forget_user();
|
||||||
return {
|
return {
|
||||||
redirectTo: "/auth/login",
|
redirectTo: "/login",
|
||||||
logout: true,
|
logout: true,
|
||||||
error: { message: "Authentication required" },
|
error: { message: "Authentication required" },
|
||||||
} as OnErrorResponse;
|
} as OnErrorResponse;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const dataProvider: DataProvider = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
create: async ({ resource, variables }) => {
|
create: async ({ resource, variables }) => {
|
||||||
const response = await fetch(`${API_URL}/${resource}`, {
|
const response = await fetch(`${API_URL}/${resource}/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(variables),
|
body: JSON.stringify(variables),
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
11
i18n/Dockerfile
Normal file
11
i18n/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM node:lts-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY app/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY app/tsconfig*.json ./
|
||||||
|
COPY app/src ./src
|
||||||
|
EXPOSE 8100
|
||||||
|
|
||||||
|
CMD [ "npm", "--watch", "start" ]
|
||||||
26
i18n/app/.gitignore
vendored
Normal file
26
i18n/app/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
\*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
|
||||||
|
.vscode/_
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
_.suo
|
||||||
|
_.ntvs_
|
||||||
|
_.njsproj
|
||||||
|
_.sln
|
||||||
|
\*.sw?
|
||||||
1164
i18n/app/package-lock.json
generated
Normal file
1164
i18n/app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
i18n/app/package.json
Normal file
25
i18n/app/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "i18n Helper",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "ts-node src/index.ts",
|
||||||
|
"build": "tsc",
|
||||||
|
"serve": "node dist/index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/node": "^22.15.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
i18n/app/public/locales/.placeholder
Normal file
0
i18n/app/public/locales/.placeholder
Normal file
49
i18n/app/src/index.ts
Normal file
49
i18n/app/src/index.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import bodyParser from "body-parser";
|
||||||
|
|
||||||
|
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
const port = process.env.PORT || 8100;
|
||||||
|
|
||||||
|
app.post('/locales/add/:lng/:ns', (req: Request, res: Response) => {
|
||||||
|
const keyPath = Object.keys(req.body)[0];
|
||||||
|
|
||||||
|
const basePath = "public/locales";
|
||||||
|
const lgPath = `${basePath}/${req.params.lng}`;
|
||||||
|
if (!existsSync(lgPath)) {
|
||||||
|
mkdirSync(lgPath);
|
||||||
|
}
|
||||||
|
const filePath = `${lgPath}/common.json`;
|
||||||
|
|
||||||
|
let missingTrans;
|
||||||
|
try {
|
||||||
|
missingTrans = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||||
|
} catch(err) {
|
||||||
|
missingTrans = JSON.parse("{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = missingTrans
|
||||||
|
const splitPath = keyPath.split(".");
|
||||||
|
for (let i=0; i < splitPath.length; i++) {
|
||||||
|
const key = splitPath[i];
|
||||||
|
if (! current.hasOwnProperty(key)) {
|
||||||
|
if (i + 1 == splitPath.length) {
|
||||||
|
current[key] = "No translation";
|
||||||
|
} else {
|
||||||
|
current[key] = JSON.parse("{}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = current[key];
|
||||||
|
}
|
||||||
|
writeFileSync(filePath, JSON.stringify(missingTrans, null, 2))
|
||||||
|
|
||||||
|
res.send("OK");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running at http://localhost:${port}`);
|
||||||
|
});
|
||||||
13
i18n/app/tsconfig.json
Normal file
13
i18n/app/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user