diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 40247fb5..38f0d48b 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -37,7 +37,7 @@ const appearanceFormSchema = z.object({ theme: z.enum(["light", "dark", "system"], { required_error: "Please select a theme.", }), - language: z.enum(["en", "pl", "ru", "zh-Hans"], { + language: z.enum(["en", "pl", "ru", "de", "zh-Hans"], { required_error: "Please select a language.", }), }); @@ -176,6 +176,7 @@ export function AppearanceForm() { { label: "English", value: "en" }, { label: "Polski", value: "pl" }, { label: "Русский", value: "ru" }, + { label: "Deutsch", value: "de" }, { label: "简体中文", value: "zh-Hans" }, ].map((preset) => ( diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index eaefab3f..6d4a88e8 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -2,7 +2,7 @@ module.exports = { i18n: { defaultLocale: "en", - locales: ["en", "pl", "ru", "zh-Hans"], + locales: ["en", "pl", "ru", "de", "zh-Hans"], localeDetection: false, }, fallbackLng: "en", diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 9b621a45..54f9238a 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -71,7 +71,7 @@ export default api.withTRPC( { i18n: { defaultLocale: "en", - locales: ["en", "pl", "ru", "zh-Hans"], + locales: ["en", "pl", "ru", "de", "zh-Hans"], localeDetection: false, }, fallbackLng: "en", diff --git a/apps/dokploy/public/locales/de/common.json b/apps/dokploy/public/locales/de/common.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/apps/dokploy/public/locales/de/common.json @@ -0,0 +1 @@ +{} diff --git a/apps/dokploy/public/locales/de/settings.json b/apps/dokploy/public/locales/de/settings.json new file mode 100644 index 00000000..e2ba0623 --- /dev/null +++ b/apps/dokploy/public/locales/de/settings.json @@ -0,0 +1,44 @@ +{ + "settings.common.save": "Speichern", + "settings.server.domain.title": "Server-Domain", + "settings.server.domain.description": "Füg eine Domain zu deiner Server-Anwendung hinzu.", + "settings.server.domain.form.domain": "Domain", + "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt E-Mail", + "settings.server.domain.form.certificate.label": "Zertifikat", + "settings.server.domain.form.certificate.placeholder": "Wähl ein Zertifikat aus", + "settings.server.domain.form.certificateOptions.none": "Keins", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Standard)", + + "settings.server.webServer.title": "Web-Server", + "settings.server.webServer.description": "Lade den Web-Server neu oder reinige ihn.", + "settings.server.webServer.actions": "Aktionen", + "settings.server.webServer.reload": "Neu laden", + "settings.server.webServer.watchLogs": "Logs anschauen", + "settings.server.webServer.updateServerIp": "Server-IP Aktualisieren", + "settings.server.webServer.server.label": "Server", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Umgebungsvariablen ändern", + "settings.server.webServer.storage.label": "Speicherplatz", + "settings.server.webServer.storage.cleanUnusedImages": "Nicht genutzte Bilder löschen", + "settings.server.webServer.storage.cleanUnusedVolumes": "Nicht genutzte Volumes löschen", + "settings.server.webServer.storage.cleanStoppedContainers": "Gestoppte Container löschen", + "settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & System bereinigen", + "settings.server.webServer.storage.cleanMonitoring": "Monitoring bereinigen", + "settings.server.webServer.storage.cleanAll": "Alles bereinigen", + + "settings.profile.title": "Konto", + "settings.profile.description": "Ändere die Details deines Profiles hier.", + "settings.profile.email": "E-Mail", + "settings.profile.password": "Passwort", + "settings.profile.avatar": "Avatar", + + "settings.appearance.title": "Aussehen", + "settings.appearance.description": "Pass das Design deines Dashboards an.", + "settings.appearance.theme": "Theme", + "settings.appearance.themeDescription": "Wähl ein Theme für dein Dashboard aus", + "settings.appearance.themes.light": "Hell", + "settings.appearance.themes.dark": "Dunkel", + "settings.appearance.themes.system": "System", + "settings.appearance.language": "Sprache", + "settings.appearance.languageDescription": "Wähl eine Sprache für dein Dashboard aus" +} diff --git a/apps/dokploy/public/templates/immich.svg b/apps/dokploy/public/templates/immich.svg new file mode 100644 index 00000000..497fbdcf --- /dev/null +++ b/apps/dokploy/public/templates/immich.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dokploy/public/templates/ryot.png b/apps/dokploy/public/templates/ryot.png new file mode 100644 index 00000000..8f03b124 Binary files /dev/null and b/apps/dokploy/public/templates/ryot.png differ diff --git a/apps/dokploy/public/templates/twenty.svg b/apps/dokploy/public/templates/twenty.svg new file mode 100644 index 00000000..cf5223b9 --- /dev/null +++ b/apps/dokploy/public/templates/twenty.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/dokploy/public/templates/yourls.svg b/apps/dokploy/public/templates/yourls.svg new file mode 100644 index 00000000..2d1d7733 --- /dev/null +++ b/apps/dokploy/public/templates/yourls.svg @@ -0,0 +1 @@ + diff --git a/apps/dokploy/templates/immich/docker-compose.yml b/apps/dokploy/templates/immich/docker-compose.yml new file mode 100644 index 00000000..2a9fb00b --- /dev/null +++ b/apps/dokploy/templates/immich/docker-compose.yml @@ -0,0 +1,111 @@ +version: "3.9" + +services: + immich-server: + image: ghcr.io/immich-app/immich-server:v1.121.0 + networks: + - dokploy-network + volumes: + - immich-library:/usr/src/app/upload + - /etc/localtime:/etc/localtime:ro + depends_on: + immich-redis: + condition: service_healthy + immich-database: + condition: service_healthy + environment: + PORT: 2283 + SERVER_URL: ${SERVER_URL} + FRONT_BASE_URL: ${FRONT_BASE_URL} + # Database Configuration + DB_HOSTNAME: ${DB_HOSTNAME} + DB_PORT: ${DB_PORT} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + DB_DATABASE_NAME: ${DB_DATABASE_NAME} + # Redis Configuration + REDIS_HOSTNAME: ${REDIS_HOSTNAME} + REDIS_PORT: ${REDIS_PORT} + REDIS_DBINDEX: ${REDIS_DBINDEX} + # Server Configuration + TZ: ${TZ} + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:2283/server-info/ping"] + interval: 30s + timeout: 10s + retries: 3 + + immich-machine-learning: + image: ghcr.io/immich-app/immich-machine-learning:v1.121.0 + networks: + - dokploy-network + volumes: + - immich-model-cache:/cache + environment: + REDIS_HOSTNAME: ${REDIS_HOSTNAME} + REDIS_PORT: ${REDIS_PORT} + REDIS_DBINDEX: ${REDIS_DBINDEX} + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3003/ping"] + interval: 30s + timeout: 10s + retries: 3 + + immich-redis: + image: redis:6.2-alpine + networks: + - dokploy-network + volumes: + - immich-redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + restart: always + + immich-database: + image: tensorchord/pgvecto-rs:pg14-v0.2.0 + networks: + - dokploy-network + volumes: + - immich-postgres:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: immich + POSTGRES_INITDB_ARGS: '--data-checksums' + healthcheck: + test: pg_isready -U ${DB_USERNAME} -d immich || exit 1 + interval: 10s + timeout: 5s + retries: 5 + command: + [ + 'postgres', + '-c', + 'shared_preload_libraries=vectors.so', + '-c', + 'search_path="$$user", public, vectors', + '-c', + 'logging_collector=on', + '-c', + 'max_wal_size=2GB', + '-c', + 'shared_buffers=512MB', + '-c', + 'wal_compression=on', + ] + restart: always + +networks: + dokploy-network: + external: true + +volumes: + immich-model-cache: + immich-postgres: + immich-library: + immich-redis-data: \ No newline at end of file diff --git a/apps/dokploy/templates/immich/index.ts b/apps/dokploy/templates/immich/index.ts new file mode 100644 index 00000000..b1b11afb --- /dev/null +++ b/apps/dokploy/templates/immich/index.ts @@ -0,0 +1,46 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generatePassword, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const dbPassword = generatePassword(); + const dbUser = "immich"; + const appSecret = generateBase64(32); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 2283, + serviceName: "immich-server", + }, + ]; + + const envs = [ + `IMMICH_HOST=${mainDomain}`, + `SERVER_URL=https://${mainDomain}`, + `FRONT_BASE_URL=https://${mainDomain}`, + "# Database Configuration", + "DB_HOSTNAME=immich-database", + "DB_PORT=5432", + `DB_USERNAME=${dbUser}`, + `DB_PASSWORD=${dbPassword}`, + "DB_DATABASE_NAME=immich", + "# Redis Configuration", + "REDIS_HOSTNAME=immich-redis", + "REDIS_PORT=6379", + "REDIS_DBINDEX=0", + "# Server Configuration", + "TZ=UTC", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/ryot/docker-compose.yml b/apps/dokploy/templates/ryot/docker-compose.yml new file mode 100644 index 00000000..1fcd80ed --- /dev/null +++ b/apps/dokploy/templates/ryot/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3.7' + +services: + ryot-app: + image: ignisda/ryot:v7.10 + networks: + - dokploy-network + environment: + - DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@ryot-db:5432/postgres + - SERVER_ADMIN_ACCESS_TOKEN=${ADMIN_ACCESS_TOKEN} + - TZ=UTC + # Optional: Uncomment and set your pro key if you have one + # - SERVER_PRO_KEY=${SERVER_PRO_KEY} + depends_on: + ryot-db: + condition: service_healthy + restart: always + pull_policy: always + + ryot-db: + image: postgres:16-alpine + networks: + - dokploy-network + volumes: + - ryot-postgres-data:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_USER=postgres + - POSTGRES_DB=postgres + - TZ=UTC + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + ryot-postgres-data: \ No newline at end of file diff --git a/apps/dokploy/templates/ryot/index.ts b/apps/dokploy/templates/ryot/index.ts new file mode 100644 index 00000000..1d8d5ce2 --- /dev/null +++ b/apps/dokploy/templates/ryot/index.ts @@ -0,0 +1,34 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generatePassword, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const postgresPassword = generatePassword(); + const adminAccessToken = generateBase64(32); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 8000, + serviceName: "ryot-app", + }, + ]; + + const envs = [ + `POSTGRES_PASSWORD=${postgresPassword}`, + `ADMIN_ACCESS_TOKEN=${adminAccessToken}`, + "# Optional: Uncomment and set your pro key if you have one", + "# SERVER_PRO_KEY=your_pro_key_here", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/stirling/docker-compose.yml b/apps/dokploy/templates/stirling/docker-compose.yml index 1784cdc7..27bd0121 100644 --- a/apps/dokploy/templates/stirling/docker-compose.yml +++ b/apps/dokploy/templates/stirling/docker-compose.yml @@ -1,12 +1,22 @@ -version: "3" - services: - soketi: - image: quay.io/soketi/soketi:1.4-16-debian - container_name: soketi + stirling-pdf: + image: frooodle/s-pdf:latest + ports: + - 8080 + volumes: + - stirling_pdf_trainingdata:/usr/share/tessdata + - stirling_pdf_extraconfigs:/configs + - stirling_pdf_customfiles:/customFiles/ + - stirling_pdf_logs:/logs/ + - stirling_pdf_pipeline:/pipeline/ environment: - SOKETI_DEBUG: "1" - SOKETI_HOST: "0.0.0.0" - SOKETI_PORT: "6001" - SOKETI_METRICS_SERVER_PORT: "9601" - restart: unless-stopped + - DOCKER_ENABLE_SECURITY=false + - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false + - LANGS=en_GB + +volumes: + stirling_pdf_trainingdata: + stirling_pdf_extraconfigs: + stirling_pdf_customfiles: + stirling_pdf_logs: + stirling_pdf_pipeline: diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 99cf534b..f6ed672c 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -881,5 +881,65 @@ export const templates: TemplateData[] = [ }, tags: ["forum", "community", "discussion"], load: () => import("./discourse/index").then((m) => m.generate), + }, + { + id: "immich", + name: "Immich", + version: "v1.121.0", + description: + "High performance self-hosted photo and video backup solution directly from your mobile phone.", + logo: "immich.svg", + links: { + github: "https://github.com/immich-app/immich", + website: "https://immich.app/", + docs: "https://immich.app/docs/overview/introduction", + }, + tags: ["photos", "videos", "backup", "media"], + load: () => import("./immich/index").then((m) => m.generate), + }, + { + id: "twenty", + name: "Twenty CRM", + version: "latest", + description: + "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", + logo: "twenty.svg", + links: { + github: "https://github.com/twentyhq/twenty", + website: "https://twenty.com", + docs: "https://docs.twenty.com", + }, + tags: ["crm", "sales", "business"], + load: () => import("./twenty/index").then((m) => m.generate), + }, + { + id: "yourls", + name: "YOURLS", + version: "1.9.2", + description: + "YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).", + logo: "yourls.svg", + links: { + github: "https://github.com/YOURLS/YOURLS", + website: "https://yourls.org/", + docs: "https://yourls.org/#documentation", + }, + tags: ["url-shortener", "php"], + load: () => import("./yourls/index").then((m) => m.generate), + }, + { + id: "ryot", + name: "Ryot", + version: "v7.10", + description: + "A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.", + logo: "ryot.png", + links: { + github: "https://github.com/IgnisDa/ryot", + website: "https://ryot.dev/", + docs: "https://ryot.dev/docs/getting-started", + }, + tags: ["media", "tracking", "self-hosted"], + load: () => import("./ryot/index").then((m) => m.generate), }, ]; diff --git a/apps/dokploy/templates/twenty/docker-compose.yml b/apps/dokploy/templates/twenty/docker-compose.yml new file mode 100644 index 00000000..34c70aeb --- /dev/null +++ b/apps/dokploy/templates/twenty/docker-compose.yml @@ -0,0 +1,104 @@ +version: "3.9" + +services: + twenty-change-vol-ownership: + image: ubuntu + user: root + networks: + - dokploy-network + volumes: + - twenty-server-local-data:/tmp/server-local-data + - twenty-docker-data:/tmp/docker-data + command: > + bash -c " + chown -R 1000:1000 /tmp/server-local-data + && chown -R 1000:1000 /tmp/docker-data" + + twenty-server: + image: twentycrm/twenty:latest + networks: + - dokploy-network + volumes: + - twenty-server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage} + - twenty-docker-data:/app/docker-data + environment: + PORT: 3000 + PG_DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@twenty-postgres:5432/twenty + SERVER_URL: https://${TWENTY_HOST} + FRONT_BASE_URL: https://${TWENTY_HOST} + REDIS_URL: redis://twenty-redis:6379 + ENABLE_DB_MIGRATIONS: "true" + SIGN_IN_PREFILLED: "true" + STORAGE_TYPE: local + APP_SECRET: ${APP_SECRET} + depends_on: + twenty-change-vol-ownership: + condition: service_completed_successfully + twenty-postgres: + condition: service_healthy + healthcheck: + test: curl --fail http://localhost:3000/healthz + interval: 5s + timeout: 5s + retries: 10 + restart: always + + twenty-worker: + image: twentycrm/twenty:latest + networks: + - dokploy-network + command: ["yarn", "worker:prod"] + environment: + PG_DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@twenty-postgres:5432/twenty + SERVER_URL: https://${TWENTY_HOST} + FRONT_BASE_URL: https://${TWENTY_HOST} + REDIS_URL: redis://twenty-redis:6379 + ENABLE_DB_MIGRATIONS: "false" + STORAGE_TYPE: local + APP_SECRET: ${APP_SECRET} + depends_on: + twenty-postgres: + condition: service_healthy + twenty-server: + condition: service_healthy + restart: always + + twenty-postgres: + image: postgres:16-alpine + networks: + - dokploy-network + volumes: + - twenty-postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: twenty + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d twenty"] + interval: 5s + timeout: 5s + retries: 10 + restart: always + + twenty-redis: + image: redis:latest + networks: + - dokploy-network + volumes: + - twenty-redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 10 + restart: always + +networks: + dokploy-network: + external: true + +volumes: + twenty-docker-data: + twenty-postgres-data: + twenty-server-local-data: + twenty-redis-data: \ No newline at end of file diff --git a/apps/dokploy/templates/twenty/index.ts b/apps/dokploy/templates/twenty/index.ts new file mode 100644 index 00000000..69bc5195 --- /dev/null +++ b/apps/dokploy/templates/twenty/index.ts @@ -0,0 +1,37 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generatePassword, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const dbPassword = generatePassword(); + const dbUser = "twenty"; + const appSecret = generateBase64(32); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 3000, + serviceName: "twenty-server", + }, + ]; + + const envs = [ + `TWENTY_HOST=${mainDomain}`, + `DB_USER=${dbUser}`, + `DB_PASSWORD=${dbPassword}`, + `APP_SECRET=${appSecret}`, + "# Optional: Configure storage path", + "# STORAGE_LOCAL_PATH=.local-storage", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/yourls/docker-compose.yml b/apps/dokploy/templates/yourls/docker-compose.yml new file mode 100644 index 00000000..ff2e14d9 --- /dev/null +++ b/apps/dokploy/templates/yourls/docker-compose.yml @@ -0,0 +1,43 @@ +version: '3.7' + +services: + yourls-app: + image: yourls:1.9.2 + networks: + - dokploy-network + environment: + YOURLS_SITE: https://${YOURLS_HOST} + YOURLS_USER: ${YOURLS_ADMIN_USER} + YOURLS_PASS: ${YOURLS_ADMIN_PASSWORD} + YOURLS_DB_HOST: yourls-mysql + YOURLS_DB_USER: yourls + YOURLS_DB_PASS: ${MYSQL_PASSWORD} + YOURLS_DB_NAME: yourls + volumes: + - yourls-data:/var/www/html + depends_on: + yourls-mysql: + condition: service_healthy + restart: always + + yourls-mysql: + image: mysql:5.7 + networks: + - dokploy-network + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: yourls + MYSQL_USER: yourls + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + volumes: + - yourls-mysql-data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u$$MYSQL_USER", "-p$$MYSQL_PASSWORD"] + interval: 10s + timeout: 5s + retries: 5 + restart: always + +volumes: + yourls-data: + yourls-mysql-data: \ No newline at end of file diff --git a/apps/dokploy/templates/yourls/index.ts b/apps/dokploy/templates/yourls/index.ts new file mode 100644 index 00000000..c96a2596 --- /dev/null +++ b/apps/dokploy/templates/yourls/index.ts @@ -0,0 +1,35 @@ +import { + type DomainSchema, + type Schema, + type Template, + generatePassword, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const mysqlPassword = generatePassword(); + const mysqlRootPassword = generatePassword(); + const adminPassword = generatePassword(); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 80, + serviceName: "yourls-app", + }, + ]; + + const envs = [ + `YOURLS_HOST=${mainDomain}`, + "YOURLS_ADMIN_USER=admin", + `YOURLS_ADMIN_PASSWORD=${adminPassword}`, + `MYSQL_PASSWORD=${mysqlPassword}`, + `MYSQL_ROOT_PASSWORD=${mysqlRootPassword}`, + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index e45a0e6e..5aa5bf62 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -1,6 +1,6 @@ import Cookies from "js-cookie"; -const SUPPORTED_LOCALES = ["en", "pl", "ru", "zh-Hans"] as const; +const SUPPORTED_LOCALES = ["en", "pl", "ru", "de", "zh-Hans"] as const; type Locale = (typeof SUPPORTED_LOCALES)[number];