// Copyright 2014 The Gogs Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package setting import ( "fmt" "net/mail" "net/url" "os" "os/exec" "path" "path/filepath" "runtime" "strconv" "strings" "time" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/user" "github.com/Unknwon/com" _ "github.com/go-macaron/cache/memcache" // memcache plugin for cache _ "github.com/go-macaron/cache/redis" "github.com/go-macaron/session" _ "github.com/go-macaron/session/redis" // redis plugin for store session _ "github.com/kardianos/minwinsvc" // import minwinsvc for windows services "gopkg.in/ini.v1" "strk.kbt.io/projects/go/libravatar" ) // Scheme describes protocol types type Scheme string // enumerates all the scheme types const ( HTTP Scheme = "http" HTTPS Scheme = "https" FCGI Scheme = "fcgi" UnixSocket Scheme = "unix" ) // LandingPage describes the default page type LandingPage string // enumerates all the landing page types const ( LandingPageHome LandingPage = "/" LandingPageExplore LandingPage = "/explore" ) // settings var ( // AppVer settings AppVer string AppName string AppURL string AppSubURL string AppSubURLDepth int // Number of slashes AppPath string AppDataPath string // Server settings Protocol Scheme Domain string HTTPAddr string HTTPPort string LocalURL string OfflineMode bool DisableRouterLog bool CertFile string KeyFile string StaticRootPath string EnableGzip bool LandingPageURL LandingPage UnixSocketPermission uint32 SSH struct { Disabled bool `ini:"DISABLE_SSH"` StartBuiltinServer bool `ini:"START_SSH_SERVER"` Domain string `ini:"SSH_DOMAIN"` Port int `ini:"SSH_PORT"` ListenHost string `ini:"SSH_LISTEN_HOST"` ListenPort int `ini:"SSH_LISTEN_PORT"` RootPath string `ini:"SSH_ROOT_PATH"` KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` KeygenPath string `ini:"SSH_KEYGEN_PATH"` MinimumKeySizeCheck bool `ini:"-"` MinimumKeySizes map[string]int `ini:"-"` } // Security settings InstallLock bool SecretKey string LogInRememberDays int CookieUserName string CookieRememberName string ReverseProxyAuthUser string // Database settings UseSQLite3 bool UseMySQL bool UseMSSQL bool UsePostgreSQL bool UseTiDB bool // Webhook settings Webhook = struct { QueueLength int DeliverTimeout int SkipTLSVerify bool Types []string PagingNum int }{ QueueLength: 1000, DeliverTimeout: 5, SkipTLSVerify: false, PagingNum: 10, } // Repository settings Repository = struct { AnsiCharset string ForcePrivate bool MaxCreationLimit int MirrorQueueLength int PullRequestQueueLength int PreferredLicenses []string DisableHTTPGit bool // Repository editor settings Editor struct { LineWrapExtensions []string PreviewableFileModes []string } `ini:"-"` // Repository upload settings Upload struct { Enabled bool TempPath string AllowedTypes []string `delim:"|"` FileMaxSize int64 MaxFiles int } `ini:"-"` }{ AnsiCharset: "", ForcePrivate: false, MaxCreationLimit: -1, MirrorQueueLength: 1000, PullRequestQueueLength: 1000, PreferredLicenses: []string{"Apache License 2.0,MIT License"}, DisableHTTPGit: false, // Repository editor settings Editor: struct { LineWrapExtensions []string PreviewableFileModes []string }{ LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","), PreviewableFileModes: []string{"markdown"}, }, // Repository upload settings Upload: struct { Enabled bool TempPath string AllowedTypes []string `delim:"|"` FileMaxSize int64 MaxFiles int }{ Enabled: true, TempPath: "data/tmp/uploads", AllowedTypes: []string{}, FileMaxSize: 3, MaxFiles: 5, }, } RepoRootPath string ScriptType = "bash" // UI settings UI = struct { ExplorePagingNum int IssuePagingNum int FeedMaxCommitNum int ThemeColorMetaTag string MaxDisplayFileSize int64 Admin struct { UserPagingNum int RepoPagingNum int NoticePagingNum int OrgPagingNum int } `ini:"ui.admin"` User struct { RepoPagingNum int } `ini:"ui.user"` }{ ExplorePagingNum: 20, IssuePagingNum: 10, FeedMaxCommitNum: 5, ThemeColorMetaTag: `#6cc644`, MaxDisplayFileSize: 8388608, Admin: struct { UserPagingNum int RepoPagingNum int NoticePagingNum int OrgPagingNum int }{ UserPagingNum: 50, RepoPagingNum: 50, NoticePagingNum: 25, OrgPagingNum: 50, }, User: struct { RepoPagingNum int }{ RepoPagingNum: 15, }, } // Markdown sttings Markdown = struct { EnableHardLineBreak bool CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` FileExtensions []string }{ EnableHardLineBreak: false, FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd", ","), } // Picture settings AvatarUploadPath string GravatarSource string DisableGravatar bool EnableFederatedAvatar bool LibravatarService *libravatar.Libravatar // Log settings LogRootPath string LogModes []string LogConfigs []string // Attachment settings AttachmentPath string AttachmentAllowedTypes string AttachmentMaxSize int64 AttachmentMaxFiles int AttachmentEnabled bool // Time settings TimeFormat string // Cache settings CacheAdapter string CacheInterval int CacheConn string // Session settings SessionConfig session.Options CSRFCookieName = "_csrf" // Cron tasks Cron = struct { UpdateMirror struct { Enabled bool RunAtStart bool Schedule string } `ini:"cron.update_mirrors"` RepoHealthCheck struct { Enabled bool RunAtStart bool Schedule string Timeout time.Duration Args []string `delim:" "` } `ini:"cron.repo_health_check"` CheckRepoStats struct { Enabled bool RunAtStart bool Schedule string } `ini:"cron.check_repo_stats"` }{ UpdateMirror: struct { Enabled bool RunAtStart bool Schedule string }{ Schedule: "@every 10m", }, RepoHealthCheck: struct { Enabled bool RunAtStart bool Schedule string Timeout time.Duration Args []string `delim:" "` }{ Schedule: "@every 24h", Timeout: 60 * time.Second, Args: []string{}, }, CheckRepoStats: struct { Enabled bool RunAtStart bool Schedule string }{ RunAtStart: true, Schedule: "@every 24h", }, } // Git settings Git = struct { DisableDiffHighlight bool MaxGitDiffLines int MaxGitDiffLineCharacters int MaxGitDiffFiles int GCArgs []string `delim:" "` Timeout struct { Migrate int Mirror int Clone int Pull int GC int `ini:"GC"` } `ini:"git.timeout"` }{ DisableDiffHighlight: false, MaxGitDiffLines: 1000, MaxGitDiffLineCharacters: 500, MaxGitDiffFiles: 100, GCArgs: []string{}, Timeout: struct { Migrate int Mirror int Clone int Pull int GC int `ini:"GC"` }{ Migrate: 600, Mirror: 300, Clone: 300, Pull: 300, GC: 60, }, } // Mirror settings Mirror = struct { DefaultInterval int }{ DefaultInterval: 8, } // API settings API = struct { MaxResponseItems int }{ MaxResponseItems: 50, } // I18n settings Langs []string Names []string dateLangs map[string]string // Highlight settings are loaded in modules/template/hightlight.go // Other settings ShowFooterBranding bool ShowFooterVersion bool ShowFooterTemplateLoadTime bool // Global setting objects Cfg *ini.File CustomPath string // Custom directory path CustomConf string ProdMode bool RunUser string IsWindows bool HasRobotsTxt bool ) // DateLang transforms standard language locale name to corresponding value in datetime plugin. func DateLang(lang string) string { name, ok := dateLangs[lang] if ok { return name } return "en" } // execPath returns the executable path. func execPath() (string, error) { file, err := exec.LookPath(os.Args[0]) if err != nil { return "", err } return filepath.Abs(file) } func init() { IsWindows = runtime.GOOS == "windows" log.NewLogger(0, "console", `{"level": 0}`) var err error if AppPath, err = execPath(); err != nil { log.Fatal(4, "fail to get app path: %v\n", err) } // Note: we don't use path.Dir here because it does not handle case // which path starts with two "/" in Windows: "//psf/Home/..." AppPath = strings.Replace(AppPath, "\\", "/", -1) } // WorkDir returns absolute path of work directory. func WorkDir() (string, error) { wd := os.Getenv("GITEA_WORK_DIR") if len(wd) > 0 { return wd, nil } // Use GOGS_WORK_DIR if available, for backward compatibility // TODO: drop in 1.1.0 ? wd = os.Getenv("GOGS_WORK_DIR") if len(wd) > 0 { log.Warn(`Usage of GOGS_WORK_DIR is deprecated and will be *removed* in a future release, please consider changing to GITEA_WORK_DIR`) return wd, nil } i := strings.LastIndex(AppPath, "/") if i == -1 { return AppPath, nil } return AppPath[:i], nil } func forcePathSeparator(path string) { if strings.Contains(path, "\\") { log.Fatal(4, "Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places") } } // IsRunUserMatchCurrentUser returns false if configured run user does not match // actual user that runs the app. The first return value is the actual user name. // This check is ignored under Windows since SSH remote login is not the main // method to login on Windows. func IsRunUserMatchCurrentUser(runUser string) (string, bool) { if IsWindows { return "", true } currentUser := user.CurrentUsername() return currentUser, runUser == currentUser } // NewContext initializes configuration context. // NOTE: do not print any log except error. func NewContext() { workDir, err := WorkDir() if err != nil { log.Fatal(4, "Fail to get work directory: %v", err) } Cfg = ini.Empty() if err != nil { log.Fatal(4, "Fail to parse 'app.ini': %v", err) } CustomPath = os.Getenv("GITEA_CUSTOM") if len(CustomPath) == 0 { // For backward compatibility // TODO: drop in 1.1.0 ? CustomPath = os.Getenv("GOGS_CUSTOM") if len(CustomPath) == 0 { CustomPath = workDir + "/custom" } else { log.Warn(`Usage of GOGS_CUSTOM is deprecated and will be *removed* in a future release, please consider changing to GITEA_CUSTOM`) } } if len(CustomConf) == 0 { CustomConf = CustomPath + "/conf/app.ini" } if com.IsFile(CustomConf) { if err = Cfg.Append(CustomConf); err != nil { log.Fatal(4, "Fail to load custom conf '%s': %v", CustomConf, err) } } else { log.Warn("Custom config '%s' not found, ignore this if you're running first time", CustomConf) } Cfg.NameMapper = ini.AllCapsUnderscore homeDir, err := com.HomeDir() if err != nil { log.Fatal(4, "Fail to get home directory: %v", err) } homeDir = strings.Replace(homeDir, "\\", "/", -1) LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log")) forcePathSeparator(LogRootPath) sec := Cfg.Section("server") AppName = Cfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea") AppURL = sec.Key("ROOT_URL").MustString("http://localhost:3000/") if AppURL[len(AppURL)-1] != '/' { AppURL += "/" } // Check if has app suburl. url, err := url.Parse(AppURL) if err != nil { log.Fatal(4, "Invalid ROOT_URL '%s': %s", AppURL, err) } // Suburl should start with '/' and end without '/', such as '/{subpath}'. // This value is empty if site does not have sub-url. AppSubURL = strings.TrimSuffix(url.Path, "/") AppSubURLDepth = strings.Count(AppSubURL, "/") Protocol = HTTP if sec.Key("PROTOCOL").String() == "https" { Protocol = HTTPS CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() } else if sec.Key("PROTOCOL").String() == "fcgi" { Protocol = FCGI } else if sec.Key("PROTOCOL").String() == "unix" { Protocol = UnixSocket UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666") UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32) if err != nil || UnixSocketPermissionParsed > 0777 { log.Fatal(4, "Fail to parse unixSocketPermission: %s", UnixSocketPermissionRaw) } UnixSocketPermission = uint32(UnixSocketPermissionParsed) } Domain = sec.Key("DOMAIN").MustString("localhost") HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPPort = sec.Key("HTTP_PORT").MustString("3000") LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(string(Protocol) + "://localhost:" + HTTPPort + "/") OfflineMode = sec.Key("OFFLINE_MODE").MustBool() DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir) AppDataPath = sec.Key("APP_DATA_PATH").MustString("data") EnableGzip = sec.Key("ENABLE_GZIP").MustBool() switch sec.Key("LANDING_PAGE").MustString("home") { case "explore": LandingPageURL = LandingPageExplore default: LandingPageURL = LandingPageHome } SSH.RootPath = path.Join(homeDir, ".ssh") SSH.KeyTestPath = os.TempDir() if err = Cfg.Section("server").MapTo(&SSH); err != nil { log.Fatal(4, "Fail to map SSH settings: %v", err) } SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") SSH.Port = sec.Key("SSH_PORT").MustInt(22) // When disable SSH, start builtin server value is ignored. if SSH.Disabled { SSH.StartBuiltinServer = false } if !SSH.Disabled && !SSH.StartBuiltinServer { if err := os.MkdirAll(SSH.RootPath, 0700); err != nil { log.Fatal(4, "Fail to create '%s': %v", SSH.RootPath, err) } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil { log.Fatal(4, "Fail to create '%s': %v", SSH.KeyTestPath, err) } } SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool() SSH.MinimumKeySizes = map[string]int{} minimumKeySizes := Cfg.Section("ssh.minimum_key_sizes").Keys() for _, key := range minimumKeySizes { if key.MustInt() != -1 { SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt() } } sec = Cfg.Section("security") InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) SecretKey = sec.Key("SECRET_KEY").MustString("!#@FDEWREWR&*(") LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible") ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") sec = Cfg.Section("attachment") AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) if !filepath.IsAbs(AttachmentPath) { AttachmentPath = path.Join(workDir, AttachmentPath) } AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1) AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4) AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5) AttachmentEnabled = sec.Key("ENABLE").MustBool(true) TimeFormat = map[string]string{ "ANSIC": time.ANSIC, "UnixDate": time.UnixDate, "RubyDate": time.RubyDate, "RFC822": time.RFC822, "RFC822Z": time.RFC822Z, "RFC850": time.RFC850, "RFC1123": time.RFC1123, "RFC1123Z": time.RFC1123Z, "RFC3339": time.RFC3339, "RFC3339Nano": time.RFC3339Nano, "Kitchen": time.Kitchen, "Stamp": time.Stamp, "StampMilli": time.StampMilli, "StampMicro": time.StampMicro, "StampNano": time.StampNano, }[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")] RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername()) // Does not check run user when the install lock is off. if InstallLock { currentUser, match := IsRunUserMatchCurrentUser(RunUser) if !match { log.Fatal(4, "Expect user '%s' but current user is: %s", RunUser, currentUser) } } // Determine and create root git repository path. sec = Cfg.Section("repository") Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool() RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gitea-repositories")) forcePathSeparator(RepoRootPath) if !filepath.IsAbs(RepoRootPath) { RepoRootPath = path.Join(workDir, RepoRootPath) } else { pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */OC.L10N.register( "lib", { "Unknown filetype" : "แแทแโแแแแถแแโแแแแแแโแฏแแแถแ", "Invalid image" : "แแผแแแถแโแแทแโแแแแนแโแแแแผแ", "today" : "แแแแแแแ", "yesterday" : "แแแแทแแแทแ", "last month" : "แแแแปแ", "_%n month ago_::_%n months ago_" : ["%n แแโแแปแ"], "last year" : "แแแแถแโแแปแ", "_%n hour ago_::_%n hours ago_" : ["%n แแแแโแแปแ"], "_%n minute ago_::_%n minutes ago_" : ["%n แแถแแธโแแปแ"], "seconds ago" : "แแทแแถแแธโแแปแ", "__language_name__" : "แแถแแถแแแแแ", "Apps" : "แแแแแแทแแธ", "Users" : "แขแแแแแแแพ", "Unknown user" : "แแทแแแแแถแแแขแแแแแแแพแแแแถแแ", "%s enter the database username." : "%s แแถแโแแแแ แผแโแแแแแโแขแแแโแแแแพโแแผแแแแแถแโแแทแแแแแแแ", "%s enter the database name." : "%s แแถแโแแแแ แผแโแแแแแโแแผแแแแแถแโแแทแแแแแแแ", "%s you may not use dots in the database name" : "%s แขแแแโแขแถแ โแแทแโแแแแพโแแแแแถโแ แปแ โแแ โแแแแปแโแแแแแโแแผแแแแแถแโแแทแแแแแแ", "Oracle connection could not be established" : "แแทแโแขแถแ โแแแแแพแโแแถแโแแแแแถแแ Oracle", "PostgreSQL username and/or password not valid" : "แแแแแโแขแแแโแแแแพ แแทแ/แฌ แแถแแแโแแแแแถแแ PostgreSQL แแบโแแทแโแแแแผแโแแ", "Set an admin username." : "แแแแแโแแแแแโแขแแแโแแแแแแแแแแ", "Set an admin password." : "แแแแแโแแถแแแโแแแแแถแแโแขแแแโแแแแแแแแแแ", "Could not find category \"%s\"" : "แแโแแทแโแแพแโแ แแแถแแโแแแแปแ \"%s\"", "A valid username must be provided" : "แแแแผแโแแแแแโแแแแแโแขแแแโแแแแพโแฒแแโแแถแโแแแแนแโแแแแผแ", "A valid password must be provided" : "แแแแผแโแแแแแโแแถแแแโแแแแแถแแโแฒแแโแแถแโแแแแนแโแแแแผแ", "Application is not enabled" : "แแทแโแแถแโแแพแโแแแแแแทแแธ", "Authentication error" : "แแแ แปแโแแถแโแแแแแโแแแแถแแโแแถแโแแแแนแโแแแแผแ" }, "nplurals=1; plural=0;");
OC.L10N.register( "lib", { "Unknown filetype" : "แแทแโแแแแถแแโแแแแแแโแฏแแแถแ", "Invalid image" : "แแผแแแถแโแแทแโแแแแนแโแแแแผแ", "today" : "แแแแแแแ", "yesterday" : "แแแแทแแแทแ", "last month" : "แแแแปแ", "_%n month ago_::_%n months ago_" : ["%n แแโแแปแ"], "last year" : "แแแแถแโแแปแ", "_%n hour ago_::_%n hours ago_" : ["%n แแแแโแแปแ"], "_%n minute ago_::_%n minutes ago_" : ["%n แแถแแธโแแปแ"], "seconds ago" : "แแทแแถแแธโแแปแ", "__language_name__" : "แแถแแถแแแแแ", "Apps" : "แแแแแแทแแธ", "Users" : "แขแแแแแแแพ", "Unknown user" : "แแทแแแแแถแแแขแแแแแแแพแแแแถแแ", "%s enter the database username." : "%s แแถแโแแแแ แผแโแแแแแโแขแแแโแแแแพโแแผแแแแแถแโแแทแแแแแแแ", "%s enter the database name." : "%s แแถแโแแแแ แผแโแแแแแโแแผแแแแแถแโแแทแแแแแแแ", "%s you may not use dots in the database name" : "%s แขแแแโแขแถแ โแแทแโแแแแพโแแแแแถโแ แปแ โแแ โแแแแปแโแแแแแโแแผแแแแแถแโแแทแแแแแแ", "Oracle connection could not be established" : "แแทแโแขแถแ โแแแแแพแโแแถแโแแแแแถแแ Oracle", "PostgreSQL username and/or password not valid" : "แแแแแโแขแแแโแแแแพ แแทแ/แฌ แแถแแแโแแแแแถแแ PostgreSQL แแบโแแทแโแแแแผแโแแ", "Set an admin username." : "แแแแแโแแแแแโแขแแแโแแแแแแแแแแ", "Set an admin password." : "แแแแแโแแถแแแโแแแแแถแแโแขแแแโแแแแแแแแแแ", "Could not find category \"%s\"" : "แแโแแทแโแแพแโแ แแแถแแโแแแแปแ \"%s\"", "A valid username must be provided" : "แแแแผแโแแแแแโแแแแแโแขแแแโแแแแพโแฒแแโแแถแโแแแแนแโแแแแผแ", "A valid password must be provided" : "แแแแผแโแแแแแโแแถแแแโแแแแแถแแโแฒแแโแแถแโแแแแนแโแแแแผแ", "Application is not enabled" : "แแทแโแแถแโแแพแโแแแแแแทแแธ", "Authentication error" : "แแแ แปแโแแถแโแแแแแโแแแแถแแโแแถแโแแแแนแโแแแแผแ" }, "nplurals=1; plural=0;");