mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 20:13:46 +00:00 
			
		
		
		
	Compare commits
	
		
			33 Commits
		
	
	
		
			shared-0.0
			...
			1.9.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fd9847ffcb | ||
|  | 2bca1d4ef0 | ||
|  | 871fdddf86 | ||
|  | bf4e01eb02 | ||
|  | 92ea9a0729 | ||
|  | a36cb9b247 | ||
|  | da71f67f85 | ||
|  | 5518abe1d6 | ||
|  | b34848447e | ||
|  | 3d5c994a32 | ||
|  | cfa2c6bf37 | ||
|  | 3d243a457f | ||
|  | e414036eb3 | ||
|  | 9ec154bbe8 | ||
|  | da479c3613 | ||
|  | 23d8f6e6b0 | ||
|  | d929ad0122 | ||
|  | 429d0f9728 | ||
|  | c905915055 | ||
|  | b238d0e75e | ||
|  | a4ebdac521 | ||
|  | bdd6e1fabe | ||
|  | d120ee25b4 | ||
|  | 14938fbf4c | ||
|  | 8babe17f8f | ||
|  | 724e1d3002 | ||
|  | a474a08195 | ||
|  | a48bbef766 | ||
|  | 71c5632e07 | ||
|  | 498b6e4670 | ||
|  | 925ddc28f4 | ||
|  | 3651b8d254 | ||
|  | 90d3cbceed | 
| @@ -54,7 +54,7 @@ author = 'The Zulip Team' | |||||||
| # The short X.Y version. | # The short X.Y version. | ||||||
| version = '1.9' | version = '1.9' | ||||||
| # The full version, including alpha/beta/rc tags. | # The full version, including alpha/beta/rc tags. | ||||||
| release = '1.9.0' | release = '1.9.1' | ||||||
|  |  | ||||||
| # This allows us to insert a warning that appears only on an unreleased | # This allows us to insert a warning that appears only on an unreleased | ||||||
| # version, e.g. to say that something is likely to have changed. | # version, e.g. to say that something is likely to have changed. | ||||||
|   | |||||||
| @@ -7,6 +7,19 @@ All notable changes to the Zulip server are documented in this file. | |||||||
| This section lists notable unreleased changes; it is generally updated | This section lists notable unreleased changes; it is generally updated | ||||||
| in bursts. | in bursts. | ||||||
|  |  | ||||||
|  | ### 1.9.1 -- 2018-11-30 | ||||||
|  |  | ||||||
|  | This release is primarily intended to improve the experience for new | ||||||
|  | Zulip installations; it has minimal changes for existing servers. | ||||||
|  |  | ||||||
|  | - Added support for getting multi-domain certificates with setup-certbot. | ||||||
|  | - Improved various installer error messages and sections of the | ||||||
|  |   installation documentation to help avoid for common mistakes. | ||||||
|  | - The Google auth integration now always offers an account chooser. | ||||||
|  | - Fixed buggy handling of avatars in Slack import. | ||||||
|  | - Fixed nginx configuration for mobile API authentication to access uploads. | ||||||
|  | - Updated translation data, including significant new Italian strings. | ||||||
|  |  | ||||||
| ### 1.9.0 -- 2018-11-07 | ### 1.9.0 -- 2018-11-07 | ||||||
|  |  | ||||||
| **Highlights:** | **Highlights:** | ||||||
|   | |||||||
| @@ -74,20 +74,117 @@ those providers, Zulip's full-text search will be unavailable. | |||||||
| ## Putting the Zulip application behind a reverse proxy | ## Putting the Zulip application behind a reverse proxy | ||||||
|  |  | ||||||
| Zulip is designed to support being run behind a reverse proxy server. | Zulip is designed to support being run behind a reverse proxy server. | ||||||
| There are few things you need to be careful about when configuring a | This section contains notes on the configuration required with | ||||||
| reverse proxy: | variable reverse proxy implementations. | ||||||
|  |  | ||||||
|  | ### Installer options | ||||||
|  |  | ||||||
|  | If your Zulip server will not be on the public Internet, we recommend, | ||||||
|  | installing with the `--self-signed-cert` option (rather than the | ||||||
|  | `--certbot` option), since CertBot requires the server to be on the | ||||||
|  | public Internet. | ||||||
|  |  | ||||||
|  | #### Configuring Zulip to allow HTTP | ||||||
|  |  | ||||||
|  | Depending on your environment, you may want the reverse proxy to talk | ||||||
|  | to the Zulip server over HTTP; this can be secure when the Zulip | ||||||
|  | server is not directly exposed to the public Internet. | ||||||
|  |  | ||||||
|  | After installing the Zulip server as | ||||||
|  | [described above](#installer-options), you can configure Zulip to talk | ||||||
|  | HTTP as follows: | ||||||
|  |  | ||||||
|  | 1. Add the following block to `/etc/zulip/zulip.conf`: | ||||||
|  |  | ||||||
|  |     ``` | ||||||
|  |     [application_server] | ||||||
|  |     http_only = true | ||||||
|  |     ``` | ||||||
|  |  | ||||||
|  | 1. As root, run | ||||||
|  | `/home/zulip/deployments/current/scripts/zulip-puppet-apply`.  This | ||||||
|  | will convert Zulip's main `nginx` configuration file to allow HTTP | ||||||
|  | instead of HTTPS. | ||||||
|  |  | ||||||
|  | 1. Finally, restart the Zulip server, using | ||||||
|  | `/home/zulip/deployments/current/scripts/restart-server`. | ||||||
|  |  | ||||||
|  | ### nginx configuration | ||||||
|  |  | ||||||
|  | You can look at our | ||||||
|  | [nginx reverse proxy configuration][nginx-loadbalancer] to see an | ||||||
|  | example of how to do this properly (the various include files are | ||||||
|  | available via the `zulip::nginx` puppet module).  Or modify this example: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | map $http_upgrade $connection_upgrade { | ||||||
|  |         default upgrade; | ||||||
|  |         ''      close; | ||||||
|  | } | ||||||
|  | server { | ||||||
|  |         listen                  443 ssl; | ||||||
|  |         server_name             zulip.example.net; | ||||||
|  |  | ||||||
|  |         ssl                     on; | ||||||
|  |         ssl_certificate         /path/to/fullchain-cert.pem; | ||||||
|  |         ssl_certificate_key     /path/to/private-key.pem; | ||||||
|  |  | ||||||
|  |         location / { | ||||||
|  |                 proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for; | ||||||
|  |                 proxy_set_header        Host $http_host; | ||||||
|  |                 proxy_set_header        Upgrade $http_upgrade; | ||||||
|  |                 proxy_set_header        Connection $connection_upgrade; | ||||||
|  |                 proxy_http_version      1.1; | ||||||
|  |                 proxy_buffering         off; | ||||||
|  |                 proxy_read_timeout      20m; | ||||||
|  |                 proxy_pass              https://zulip-upstream-host; | ||||||
|  |         } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Don't forget to update `server_name`, `ssl_certificate`, | ||||||
|  | `ssl_certificate_key` and `proxy_pass` with propper values. | ||||||
|  |  | ||||||
|  | [nginx-proxy-config]: https://github.com/zulip/zulip/blob/master/puppet/zulip/files/nginx/zulip-include-common/proxy | ||||||
|  | [nginx-proxy-longpolling-config]: https://github.com/zulip/zulip/blob/master/puppet/zulip/files/nginx/zulip-include-common/proxy_longpolling | ||||||
|  | [voyager.pp]: https://github.com/zulip/zulip/blob/master/puppet/zulip/manifests/voyager.pp | ||||||
|  | [zulipchat-puppet]: https://github.com/zulip/zulip/tree/master/puppet/zulip_ops/manifests | ||||||
|  | [nginx-loadbalancer]: https://github.com/zulip/zulip/blob/master/puppet/zulip_ops/files/nginx/sites-available/loadbalancer | ||||||
|  |  | ||||||
|  | ### HAProxy configuration | ||||||
|  |  | ||||||
|  | If you want to use HAProxy with Zulip, this `backend` config is a good | ||||||
|  | place to start. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | backend zulip | ||||||
|  |     mode http | ||||||
|  |     balance leastconn | ||||||
|  |     http-request set-header X-Client-IP %[src] | ||||||
|  |     reqadd X-Forwarded-Proto:\ https | ||||||
|  |     server zulip 10.10.10.10:80 check | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Since this configuration uses the `http` mode, you will also need to | ||||||
|  | [configure Zulip to allow HTTP](#configuring-zulip-to-allow-http) as | ||||||
|  | described above. | ||||||
|  |  | ||||||
|  | ### Other proxies | ||||||
|  |  | ||||||
|  | If you're using another reverse proxy implementation, there are few | ||||||
|  | things you need to be careful about when configuring it: | ||||||
|  |  | ||||||
| 1. Configure your reverse proxy (or proxies) to correctly maintain the | 1. Configure your reverse proxy (or proxies) to correctly maintain the | ||||||
| `X-Forwarded-For` HTTP header, which is supposed to contain the series | `X-Forwarded-For` HTTP header, which is supposed to contain the series | ||||||
| of IP addresses the request was forwarded through.  This | of IP addresses the request was forwarded through.  You can verify | ||||||
| [nginx code snippet][nginx-proxy-config] will do the right thing, and | your work by looking at `/var/log/zulip/server.log` and checking it | ||||||
| you can verify your work by looking at `/var/log/zulip/server.log` and | has the actual IP addresses of clients, not the IP address of the | ||||||
| checking it has the actual IP addresses of clients, not the IP address | proxy server. | ||||||
| of the proxy server. |  | ||||||
|  |  | ||||||
| 2. Ensure your proxy doesn't interfere with Zulip's use of long-polling | 2. Ensure your proxy doesn't interfere with Zulip's use of | ||||||
| for real-time push from the server to your users' browsers.  This | long-polling for real-time push from the server to your users' | ||||||
| [nginx code snippet][nginx-proxy-longpolling-config] will do the right thing. | browsers.  This [nginx code snippet][nginx-proxy-longpolling-config] | ||||||
|  | does this. | ||||||
|  |  | ||||||
| The key configuration options are, for the `/json/events` and | The key configuration options are, for the `/json/events` and | ||||||
| `/api/1/events` endpoints: | `/api/1/events` endpoints: | ||||||
| @@ -97,22 +194,11 @@ The key configuration options are, for the `/json/events` and | |||||||
| * `proxy_buffering off`.  If you don't do this, your `nginx` proxy may | * `proxy_buffering off`.  If you don't do this, your `nginx` proxy may | ||||||
|   return occasional 502 errors to clients using Zulip's events API. |   return occasional 502 errors to clients using Zulip's events API. | ||||||
|  |  | ||||||
| 3. The other tricky failure mode with `nginx` reverse proxies is that | 3. The other tricky failure mode we've seen with `nginx` reverse | ||||||
| they can load-balance between the IPv4 and IPv6 addresses for a given | proxies is that they can load-balance between the IPv4 and IPv6 | ||||||
| hostname.  This can result in mysterious errors that can be quite | addresses for a given hostname.  This can result in mysterious errors | ||||||
| difficult to debug.  Be sure to declare your `upstreams` in a way that | that can be quite difficult to debug.  Be sure to declare your | ||||||
| won't do load-balancing unexpectedly (e.g. pointing to a DNS name that | `upstreams` equivalent in a way that won't do load-balancing | ||||||
| you haven't configured with multiple IPs for your Zulip machine; | unexpectedly (e.g. pointing to a DNS name that you haven't configured | ||||||
| sometimes this happens with IPv6 configuration). | with multiple IPs for your Zulip machine; sometimes this happens with | ||||||
|  | IPv6 configuration). | ||||||
| You can look at our |  | ||||||
| [nginx reverse proxy configuration][nginx-loadbalancer] to see an |  | ||||||
| example of how to do this properly (the various include files are |  | ||||||
| available via the `zulip::nginx` puppet module). |  | ||||||
|  |  | ||||||
| [nginx-proxy-config]: https://github.com/zulip/zulip/blob/master/puppet/zulip/files/nginx/zulip-include-common/proxy |  | ||||||
| [nginx-proxy-longpolling-config]: https://github.com/zulip/zulip/blob/master/puppet/zulip/files/nginx/zulip-include-common/proxy_longpolling |  | ||||||
| [voyager.pp]: https://github.com/zulip/zulip/blob/master/puppet/zulip/manifests/voyager.pp |  | ||||||
| [zulipchat-puppet]: https://github.com/zulip/zulip/tree/master/puppet/zulip_ops/manifests |  | ||||||
| [nginx-loadbalancer]: https://github.com/zulip/zulip/blob/master/puppet/zulip_ops/files/nginx/sites-available/loadbalancer |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -55,6 +55,30 @@ follows: | |||||||
|   providers |   providers | ||||||
| * The password like `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`. | * The password like `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`. | ||||||
|  |  | ||||||
|  | ### Using system email | ||||||
|  |  | ||||||
|  | If you'd like to send outgoing email using the local operating | ||||||
|  | system's email delivery configuration (e.g. you have `postfix` | ||||||
|  | configuration on the system that forwards email sent locally into your | ||||||
|  | corporate email system), you will likely need to use something like | ||||||
|  | these setting values: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | EMAIL_HOST = 'localhost' | ||||||
|  | EMAIL_PORT = 25 | ||||||
|  | EMAIL_USE_TLS = False | ||||||
|  | EMAIL_HOST_USER = "" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | We should emphasize that because modern spam filtering is very | ||||||
|  | aggressive, you should make sure your downstream email system is | ||||||
|  | configured to properly sign outgoing email sent by your Zulip server | ||||||
|  | (or check your spam folder) when using this configuration.  See | ||||||
|  | [documentation on using Django with a local postfix server][postfix-email] | ||||||
|  | for additional advice. | ||||||
|  |  | ||||||
|  | [postfix-email]: https://stackoverflow.com/questions/26333009/how-do-you-configure-django-to-send-mail-through-postfix | ||||||
|  |  | ||||||
| ### Using Gmail for outgoing email | ### Using Gmail for outgoing email | ||||||
|  |  | ||||||
| We don't recommend using an inbox product like Gmail for outgoing | We don't recommend using an inbox product like Gmail for outgoing | ||||||
|   | |||||||
| @@ -55,6 +55,16 @@ archive of all the organization's uploaded files. | |||||||
|  |  | ||||||
| ## Import into a new Zulip server | ## Import into a new Zulip server | ||||||
|  |  | ||||||
|  | The Zulip server you're importing into needs to be running the same | ||||||
|  | version of Zulip as the server you exported from, so that the same | ||||||
|  | formats are consistent.  For exports from zulipchat.com, usually this | ||||||
|  | means you need to upgrade your Zulip server to the latest `master` | ||||||
|  | branch, using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | ||||||
|  |  | ||||||
|  | First [install a new Zulip server](../production/install.html), | ||||||
|  | skipping "Step 3: Create a Zulip organization, and log in" (you'll | ||||||
|  | create your Zulip organization via the data import tool instead). | ||||||
|  |  | ||||||
| Log in to a shell on your Zulip server as the `zulip` user. Run the | Log in to a shell on your Zulip server as the `zulip` user. Run the | ||||||
| following commands, replacing the filename with the path to your data | following commands, replacing the filename with the path to your data | ||||||
| export tarball: | export tarball: | ||||||
|   | |||||||
| @@ -25,9 +25,15 @@ follows: | |||||||
|    If you installed your Zulip server with a version older than 1.6, |    If you installed your Zulip server with a version older than 1.6, | ||||||
|    you'll need to add the line (it won't be there to uncomment). |    you'll need to add the line (it won't be there to uncomment). | ||||||
|  |  | ||||||
| 1. If you're running Zulip 1.8.1 or newer, you can run `manage.py | 1. If you're running Zulip 1.8.1 or newer, you can run the | ||||||
|    register_server` from `/home/zulip/deployments/current`.  This |     registration command: | ||||||
|    command will print the registration data it would send to the |     ``` | ||||||
|  |     # As root: | ||||||
|  |     su zulip -c '/home/zulip/deployments/current/manage.py register_server' | ||||||
|  |     # Or as the zulip user, you can skip the `su zulip -c`: | ||||||
|  |     /home/zulip/deployments/current/manage.py register_server | ||||||
|  |     ``` | ||||||
|  |    This command will print the registration data it would send to the | ||||||
|    mobile push notifications service, ask you to accept the terms of |    mobile push notifications service, ask you to accept the terms of | ||||||
|    service, and if you accept, register your server.  Otherwise, see |    service, and if you accept, register your server.  Otherwise, see | ||||||
|    the [legacy signup instructions](#legacy-signup). |    the [legacy signup instructions](#legacy-signup). | ||||||
|   | |||||||
| @@ -50,6 +50,21 @@ For servers hosting a large number of organizations, like | |||||||
| `ROOT_DOMAIN_LANDING_PAGE = True` in `/etc/zulip/settings.py` so that | `ROOT_DOMAIN_LANDING_PAGE = True` in `/etc/zulip/settings.py` so that | ||||||
| the homepage for the server is a copy of the Zulip homepage. | the homepage for the server is a copy of the Zulip homepage. | ||||||
|  |  | ||||||
|  | ### SSL Certificates | ||||||
|  |  | ||||||
|  | You'll need to install an SSL certificate valid for all the | ||||||
|  | (sub)domains you're using your Zulip server with.  You can get an SSL | ||||||
|  | certificate covering several domains for free by using | ||||||
|  | [our Certbot wrapper tool](../production/ssl-certificates.html#after-zulip-is-already-installed), | ||||||
|  | though if you're going to host a large number of organizations, you | ||||||
|  | may want to get a wildcard certificate.  You can also get a wildcard | ||||||
|  | certificate for | ||||||
|  | [free using Certbot](https://community.letsencrypt.org/t/getting-wildcard-certificates-with-certbot/56285), | ||||||
|  | but because of the stricter security checks for acquiring a wildcard | ||||||
|  | cert, it isn't possible for a generic script like `setup-certbot` to | ||||||
|  | create it for you; you'll have to do some manual steps with your DNS | ||||||
|  | provider. | ||||||
|  |  | ||||||
| ### Other hostnames | ### Other hostnames | ||||||
|  |  | ||||||
| If you'd like to use hostnames that are not subdomains of each other, | If you'd like to use hostnames that are not subdomains of each other, | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ strength allowed is controlled by two settings in | |||||||
|     figure out whether a stream with that name exists, but cannot see any |     figure out whether a stream with that name exists, but cannot see any | ||||||
|     other details about the stream. |     other details about the stream. | ||||||
|  |  | ||||||
|   * See [Stream permissions](/help/stream-permissions) for more details. |   * See [Stream permissions](https://zulipchat.com/help/stream-permissions) for more details. | ||||||
|  |  | ||||||
| * Zulip supports editing the content and topics of messages that have | * Zulip supports editing the content and topics of messages that have | ||||||
|   already been sent. As a general philosophy, our policies provide |   already been sent. As a general philosophy, our policies provide | ||||||
| @@ -129,7 +129,7 @@ strength allowed is controlled by two settings in | |||||||
|     any time by that administrator. |     any time by that administrator. | ||||||
|  |  | ||||||
|   * See |   * See | ||||||
|     [Configuring message editing and deletion](/help/configure-message-editing-and-deletion) |     [Configuring message editing and deletion](https://zulipchat.com/help/configure-message-editing-and-deletion) | ||||||
|     for more details. |     for more details. | ||||||
|  |  | ||||||
| ## Users and Bots | ## Users and Bots | ||||||
| @@ -146,7 +146,7 @@ strength allowed is controlled by two settings in | |||||||
|   exceptions: |   exceptions: | ||||||
|  |  | ||||||
|   * Administrators may get access to private messages via some types of |   * Administrators may get access to private messages via some types of | ||||||
|     [data export](/help/export-your-organization). |     [data export](https://zulipchat.com/help/export-your-organization). | ||||||
|  |  | ||||||
|   * Administrators can change the ownership of a bot. If a bot is subscribed |   * Administrators can change the ownership of a bot. If a bot is subscribed | ||||||
|     to a private stream, then an administrator can indirectly get access to |     to a private stream, then an administrator can indirectly get access to | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ administrator can do.  To change any of the following settings, edit | |||||||
| the `/etc/zulip/settings.py` file on your Zulip server, and then | the `/etc/zulip/settings.py` file on your Zulip server, and then | ||||||
| restart the server with the following command: | restart the server with the following command: | ||||||
| ``` | ``` | ||||||
| su zulip -c /home/zulip/deployments/current/scripts/restart-server | su zulip -c '/home/zulip/deployments/current/scripts/restart-server' | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Specific settings | ## Specific settings | ||||||
|   | |||||||
| @@ -72,6 +72,9 @@ The `--hostname` and `--email` options are required when using | |||||||
| Zulip server machine to be reachable by that name from the public | Zulip server machine to be reachable by that name from the public | ||||||
| Internet. | Internet. | ||||||
|  |  | ||||||
|  | If you need to configure a multiple domain certificate, you can generate | ||||||
|  | one as described in the section below after installing Zulip. | ||||||
|  |  | ||||||
| [doc-install-script]: ../production/install.html#step-2-install-zulip | [doc-install-script]: ../production/install.html#step-2-install-zulip | ||||||
|  |  | ||||||
| ### After Zulip is already installed | ### After Zulip is already installed | ||||||
| @@ -80,11 +83,12 @@ To enable the Certbot automation on an already-installed Zulip | |||||||
| server, run the following commands: | server, run the following commands: | ||||||
| ``` | ``` | ||||||
| sudo -s  # If not already root | sudo -s  # If not already root | ||||||
| /home/zulip/deployments/current/scripts/setup/setup-certbot --hostname=HOSTNAME --email=EMAIL | /home/zulip/deployments/current/scripts/setup/setup-certbot --email=EMAIL HOSTNAME [HOSTNAME2...] | ||||||
| ``` | ``` | ||||||
| where HOSTNAME is the domain name users see in their browser when | where HOSTNAME is the domain name users see in their browser when | ||||||
| using the server (e.g., `zulip.example.com`), and EMAIL is a contact | using the server (e.g., `zulip.example.com`), and EMAIL is a contact | ||||||
| address for the server admins. | address for the server admins. Additional hostnames can also be | ||||||
|  | specified to issue a certificate for multiple domains. | ||||||
|  |  | ||||||
| ### How it works | ### How it works | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,25 +35,35 @@ created (e.g. `exampleinc-zulip-uploads`). | |||||||
| 1. Comment out the `LOCAL_UPLOADS_DIR` setting in | 1. Comment out the `LOCAL_UPLOADS_DIR` setting in | ||||||
| `/etc/zulip/settings.py` (add a `#` at the start of the line). | `/etc/zulip/settings.py` (add a `#` at the start of the line). | ||||||
|  |  | ||||||
|  | 1. In some AWS regions, you need to explicitly | ||||||
|  |     [configure boto](http://boto.cloudhackers.com/en/latest/boto_config_tut.html) | ||||||
|  |     to use AWS's SIGv4 signature format (because AWS has stopped | ||||||
|  |     supporting the older v3 format in those regions).  You can do this | ||||||
|  |     by adding an `/etc/boto.cfg` containing the following: | ||||||
|  |     ``` | ||||||
|  |     [s3] | ||||||
|  |     use-sigv4 = True | ||||||
|  |     ``` | ||||||
|  |  | ||||||
| 1. You will need to configure `nginx` to direct requests for uploaded | 1. You will need to configure `nginx` to direct requests for uploaded | ||||||
| files to the Zulip server (which will then serve a redirect to the |     files to the Zulip server (which will then serve a redirect to the | ||||||
| appropriate place in S3), rather than serving them directly. |     appropriate place in S3), rather than serving them directly. | ||||||
|  |  | ||||||
| With Zulip 1.9.0 and newer, you can do this automatically with the |     With Zulip 1.9.0 and newer, you can do this automatically with the | ||||||
| following commands run as root: |     following commands run as root: | ||||||
|  |  | ||||||
| ``` |     ``` | ||||||
| crudini --set /etc/zulip/zulip.conf application_server no_serve_uploads true |     crudini --set /etc/zulip/zulip.conf application_server no_serve_uploads true | ||||||
| /home/zulip/deployments/current/scripts/zulip-puppet-apply |     /home/zulip/deployments/current/scripts/zulip-puppet-apply | ||||||
| ``` |     ``` | ||||||
|  |  | ||||||
| (The first line will update your `/etc/zulip/zulip.conf`). |     (The first line will update your `/etc/zulip/zulip.conf`). | ||||||
|  |  | ||||||
| With older Zulip, you need to edit |     With older Zulip, you need to edit | ||||||
| `/etc/nginx/sites-available/zulip-enterprise` to comment out the |     `/etc/nginx/sites-available/zulip-enterprise` to comment out the | ||||||
| `nginx` configuration block for `/user_avatars` and the `include |     `nginx` configuration block for `/user_avatars` and the `include | ||||||
| /etc/nginx/zulip-include/uploads.route` line and then reload the |     /etc/nginx/zulip-include/uploads.route` line and then reload the | ||||||
| `nginx` service (`service nginx reload`). |     `nginx` service (`service nginx reload`). | ||||||
|  |  | ||||||
| 1. Finally, restart the Zulip server so that your settings changes | 1. Finally, restart the Zulip server so that your settings changes | ||||||
|    take effect |    take effect | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ messages until the migration finishes. | |||||||
|  |  | ||||||
| Once the migrations are complete, restart Zulip: | Once the migrations are complete, restart Zulip: | ||||||
|  |  | ||||||
|     su zulip -c /home/zulip/deployments/current/scripts/restart-server |     su zulip -c '/home/zulip/deployments/current/scripts/restart-server' | ||||||
|  |  | ||||||
| Now, you can use full-text search across all languages. | Now, you can use full-text search across all languages. | ||||||
|  |  | ||||||
| @@ -100,7 +100,7 @@ Then, set `USING_PGROONGA = False` in `/etc/zulip/settings.py`: | |||||||
|  |  | ||||||
| And, restart Zulip: | And, restart Zulip: | ||||||
|  |  | ||||||
|     su zulip -c /home/zulip/deployments/current/scripts/restart-server |     su zulip -c '/home/zulip/deployments/current/scripts/restart-server' | ||||||
|  |  | ||||||
| Now, full-text search feature based on PGroonga is disabled.  If you'd | Now, full-text search feature based on PGroonga is disabled.  If you'd | ||||||
| like, you can also remove the `pgroonga = enabled` line in | like, you can also remove the `pgroonga = enabled` line in | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								manage.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								manage.py
									
									
									
									
									
								
							| @@ -1,16 +1,20 @@ | |||||||
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||||
|  | from __future__ import (print_function) | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| import types | import types | ||||||
|  | if sys.version_info <= (3, 0): | ||||||
|  |     print("Error: Zulip is a Python 3 project, and cannot be run with Python 2.") | ||||||
|  |     print("Use e.g. `/path/to/manage.py` not `python /path/to/manage.py`.") | ||||||
|  |     sys.exit(1) | ||||||
|  |  | ||||||
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | ||||||
| sys.path.append(BASE_DIR) | sys.path.append(BASE_DIR) | ||||||
| import scripts.lib.setup_path_on_import | import scripts.lib.setup_path_on_import | ||||||
|  | from scripts.lib.zulip_tools import script_should_not_be_root | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     if 'posix' in os.name and os.geteuid() == 0: |     script_should_not_be_root() | ||||||
|         print("manage.py should not be run as root.  Use `su zulip` to drop root.") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if (os.access('/etc/zulip/zulip.conf', os.R_OK) and not |     if (os.access('/etc/zulip/zulip.conf', os.R_OK) and not | ||||||
|             os.access('/etc/zulip/zulip-secrets.conf', os.R_OK)): |             os.access('/etc/zulip/zulip-secrets.conf', os.R_OK)): | ||||||
|         # The best way to detect running manage.py as another user in |         # The best way to detect running manage.py as another user in | ||||||
|   | |||||||
| @@ -60,14 +60,28 @@ location / { | |||||||
|     uwsgi_pass django; |     uwsgi_pass django; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # Certain Django routes not under /api are shared between mobile and | ||||||
|  | # web and thus need API headers added.  We don't collapse this with the | ||||||
|  | # above block for /events, because regular expressions take priority over | ||||||
|  | # paths in nginx's order-of-operations, and we don't want to override the | ||||||
|  | # tornado stuff. | ||||||
|  | location ~ ^/(user_uploads|avatar|thumbnail)/ { | ||||||
|  |     add_header Access-Control-Allow-Origin *; | ||||||
|  |     add_header Access-Control-Allow-Headers Authorization; | ||||||
|  |     add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD'; | ||||||
|  |  | ||||||
|  |     include uwsgi_params; | ||||||
|  |     uwsgi_pass django; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Send all API routes not covered above to Django via uWSGI | ||||||
| location /api/ { | location /api/ { | ||||||
|     add_header Access-Control-Allow-Origin *; |     add_header Access-Control-Allow-Origin *; | ||||||
|     add_header Access-Control-Allow-Headers Authorization; |     add_header Access-Control-Allow-Headers Authorization; | ||||||
|     add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD'; |     add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD'; | ||||||
|  |  | ||||||
|     include uwsgi_params; |     include uwsgi_params; | ||||||
|     uwsgi_pass  django; |     uwsgi_pass django; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| include /etc/nginx/zulip-include/app.d/*.conf; | include /etc/nginx/zulip-include/app.d/*.conf; | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ PUPPET_CLASSES="${PUPPET_CLASSES:-zulip::voyager}" | |||||||
| VIRTUALENV_NEEDED="${VIRTUALENV_NEEDED:-yes}" | VIRTUALENV_NEEDED="${VIRTUALENV_NEEDED:-yes}" | ||||||
|  |  | ||||||
| if [ -n "$SELF_SIGNED_CERT" ] && [ -n "$USE_CERTBOT" ]; then | if [ -n "$SELF_SIGNED_CERT" ] && [ -n "$USE_CERTBOT" ]; then | ||||||
|  |     set +x | ||||||
|     echo "error: --self-signed-cert and --certbot are incompatible" >&2 |     echo "error: --self-signed-cert and --certbot are incompatible" >&2 | ||||||
|     echo >&2 |     echo >&2 | ||||||
|     usage |     usage | ||||||
| @@ -80,14 +81,16 @@ export LANGUAGE="en_US.UTF-8" | |||||||
|  |  | ||||||
| # Check for a supported OS release. | # Check for a supported OS release. | ||||||
| apt-get install -y lsb-release sudo | apt-get install -y lsb-release sudo | ||||||
| os_release="$(lsb_release -sc)" | os_info="$(lsb_release --short --id --release --codename)" | ||||||
| case "$os_release" in | { read -r os_id; read -r os_release; read -r os_codename; } <<< "$os_info" | ||||||
|  |  | ||||||
|  | case "$os_codename" in | ||||||
|     trusty|xenial|stretch|bionic) ;; |     trusty|xenial|stretch|bionic) ;; | ||||||
|     *) |     *) | ||||||
|         set +x |         set +x | ||||||
|         cat <<EOF |         cat <<EOF | ||||||
|  |  | ||||||
| Unsupported OS release: $os_release | Unsupported OS release: $os_codename | ||||||
|  |  | ||||||
| Zulip in production is supported only on: | Zulip in production is supported only on: | ||||||
|  - Debian 9 "stretch" |  - Debian 9 "stretch" | ||||||
| @@ -101,12 +104,30 @@ EOF | |||||||
|         exit 1 |         exit 1 | ||||||
| esac | esac | ||||||
|  |  | ||||||
|  | if [ "$os_id" = Ubuntu ] && ! apt-cache policy | | ||||||
|  |            grep -q "^     release v=$os_release,o=Ubuntu,a=$os_codename,n=$os_codename,l=Ubuntu,c=universe"; then | ||||||
|  |     set +x | ||||||
|  |     cat <<'EOF' | ||||||
|  |  | ||||||
|  | You must enable the Ubuntu Universe repository before installing | ||||||
|  | Zulip.  You can do this with: `add-apt-repository universe`. | ||||||
|  |  | ||||||
|  | For more information, see: | ||||||
|  |   https://zulip.readthedocs.io/en/latest/production/requirements.html | ||||||
|  | EOF | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
| # Check for at least ~1.9GB of RAM before starting installation; | # Check for at least ~1.9GB of RAM before starting installation; | ||||||
| # otherwise users will find out about insufficient RAM via weird | # otherwise users will find out about insufficient RAM via weird | ||||||
| # errors like a segfault running `pip install`. | # errors like a segfault running `pip install`. | ||||||
| mem_kb=$(head -n1 /proc/meminfo | awk '{print $2}') | mem_kb=$(head -n1 /proc/meminfo | awk '{print $2}') | ||||||
| if [ "$mem_kb" -lt 1900000 ]; then | if [ "$mem_kb" -lt 1900000 ]; then | ||||||
|     echo "Insufficient RAM.  Zulip requires at least 2GB of RAM." |     set +x | ||||||
|  |     echo -e '\033[0;31m' >&2 | ||||||
|  |     echo "Insufficient RAM.  Zulip requires at least 2GB of RAM." >&2 | ||||||
|  |     echo >&2 | ||||||
|  |     echo -e '\033[0m' >&2 | ||||||
|     exit 1 |     exit 1 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| @@ -141,15 +162,22 @@ EOF | |||||||
| fi | fi | ||||||
|  |  | ||||||
| apt-get -y dist-upgrade "${APT_OPTIONS[@]}" | apt-get -y dist-upgrade "${APT_OPTIONS[@]}" | ||||||
| apt-get install -y \ | if ! apt-get install -y \ | ||||||
|     puppet git curl wget \ |     puppet git curl wget \ | ||||||
|     python python3 python-six python3-six crudini \ |     python python3 python-six python3-six crudini \ | ||||||
|     "${ADDITIONAL_PACKAGES[@]}" |     "${ADDITIONAL_PACKAGES[@]}"; then | ||||||
|  |     set +x | ||||||
|  |     echo -e '\033[0;31m' >&2 | ||||||
|  |     echo "Installing packages failed; is network working and (on Ubuntu) the universe repository enabled?" >&2 | ||||||
|  |     echo >&2 | ||||||
|  |     echo -e '\033[0m' >&2 | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
| if [ -n "$USE_CERTBOT" ]; then | if [ -n "$USE_CERTBOT" ]; then | ||||||
|     "$ZULIP_PATH"/scripts/setup/setup-certbot \ |     "$ZULIP_PATH"/scripts/setup/setup-certbot \ | ||||||
|         --no-zulip-conf --method=standalone \ |         --no-zulip-conf --method=standalone \ | ||||||
|         --hostname "$EXTERNAL_HOST" --email "$ZULIP_ADMINISTRATOR" |         "$EXTERNAL_HOST" --email "$ZULIP_ADMINISTRATOR" | ||||||
| elif [ -n "$SELF_SIGNED_CERT" ]; then | elif [ -n "$SELF_SIGNED_CERT" ]; then | ||||||
|     "$ZULIP_PATH"/scripts/setup/generate-self-signed-cert \ |     "$ZULIP_PATH"/scripts/setup/generate-self-signed-cert \ | ||||||
|         --exists-ok "${EXTERNAL_HOST:-$(hostname)}" |         --exists-ok "${EXTERNAL_HOST:-$(hostname)}" | ||||||
| @@ -303,7 +331,7 @@ if [ "$has_appserver" = 0 ]; then | |||||||
|         # If we're installing from a git checkout, we need to run |         # If we're installing from a git checkout, we need to run | ||||||
|         # `tools/update-prod-static` in order to build the static |         # `tools/update-prod-static` in order to build the static | ||||||
|         # assets. |         # assets. | ||||||
|         su zulip -c "/home/zulip/deployments/current/tools/update-prod-static --authors-not-required" |         su zulip -c '/home/zulip/deployments/current/tools/update-prod-static --authors-not-required' | ||||||
|     fi |     fi | ||||||
| fi | fi | ||||||
|  |  | ||||||
| @@ -319,7 +347,7 @@ if [ -n "$NO_INIT_DB" ]; then | |||||||
|  |  | ||||||
|  Stopping because --no-init-db was passed.  To complete the installation, run: |  Stopping because --no-init-db was passed.  To complete the installation, run: | ||||||
|  |  | ||||||
|    su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database |    su zulip -c '/home/zulip/deployments/current/scripts/setup/initialize-database' | ||||||
| EOF | EOF | ||||||
|     exit 0 |     exit 0 | ||||||
| fi | fi | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ SOURCES_FILE=/etc/apt/sources.list.d/zulip.list | |||||||
| STAMP_FILE=/etc/apt/sources.list.d/zulip.list.apt-update-in-progress | STAMP_FILE=/etc/apt/sources.list.d/zulip.list.apt-update-in-progress | ||||||
| zulip_source_hash=$(sha1sum "$SOURCES_FILE") | zulip_source_hash=$(sha1sum "$SOURCES_FILE") | ||||||
|  |  | ||||||
| apt-get install -y lsb-release apt-transport-https | apt-get install -y lsb-release apt-transport-https gnupg | ||||||
|  |  | ||||||
| SCRIPTS_PATH="$(dirname "$(dirname "$0")")" | SCRIPTS_PATH="$(dirname "$(dirname "$0")")" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,16 +11,14 @@ os.environ["PYTHONUNBUFFERED"] = "y" | |||||||
|  |  | ||||||
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | ||||||
| from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, \ | from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, \ | ||||||
|     su_to_zulip, get_deployment_lock, release_deployment_lock |     su_to_zulip, get_deployment_lock, release_deployment_lock, script_should_be_root | ||||||
|  |  | ||||||
|  | script_should_be_root(strip_lib_from_paths=True) | ||||||
|  |  | ||||||
| logging.Formatter.converter = time.gmtime | logging.Formatter.converter = time.gmtime | ||||||
| logging.basicConfig(format="%(asctime)s upgrade-zulip: %(message)s", | logging.basicConfig(format="%(asctime)s upgrade-zulip: %(message)s", | ||||||
|                     level=logging.INFO) |                     level=logging.INFO) | ||||||
|  |  | ||||||
| if os.getuid() != 0: |  | ||||||
|     logging.error("Must be run as root.") |  | ||||||
|     sys.exit(1) |  | ||||||
|  |  | ||||||
| if len(sys.argv) != 2: | if len(sys.argv) != 2: | ||||||
|     print(FAIL + "Usage: %s <tarball>" % (sys.argv[0],) + ENDC) |     print(FAIL + "Usage: %s <tarball>" % (sys.argv[0],) + ENDC) | ||||||
|     sys.exit(1) |     sys.exit(1) | ||||||
|   | |||||||
| @@ -24,16 +24,14 @@ os.environ["PYTHONUNBUFFERED"] = "y" | |||||||
|  |  | ||||||
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | ||||||
| from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, make_deploy_path, \ | from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, make_deploy_path, \ | ||||||
|     get_deployment_lock, release_deployment_lock, su_to_zulip |     get_deployment_lock, release_deployment_lock, su_to_zulip, script_should_be_root | ||||||
|  |  | ||||||
|  | script_should_be_root(strip_lib_from_paths=True) | ||||||
|  |  | ||||||
| logging.Formatter.converter = time.gmtime | logging.Formatter.converter = time.gmtime | ||||||
| logging.basicConfig(format="%(asctime)s upgrade-zulip-from-git: %(message)s", | logging.basicConfig(format="%(asctime)s upgrade-zulip-from-git: %(message)s", | ||||||
|                     level=logging.INFO) |                     level=logging.INFO) | ||||||
|  |  | ||||||
| if os.getuid() != 0: |  | ||||||
|     logging.error("Must be run as root.") |  | ||||||
|     sys.exit(1) |  | ||||||
|  |  | ||||||
| parser = argparse.ArgumentParser() | parser = argparse.ArgumentParser() | ||||||
| parser.add_argument("refname", help="Git reference, e.g. a branch, tag, or commit ID.") | parser.add_argument("refname", help="Git reference, e.g. a branch, tag, or commit ID.") | ||||||
| parser.add_argument("--remote-url", dest="remote_url", | parser.add_argument("--remote-url", dest="remote_url", | ||||||
|   | |||||||
| @@ -20,16 +20,14 @@ os.environ["LANG"] = "en_US.UTF-8" | |||||||
| os.environ["LANGUAGE"] = "en_US.UTF-8" | os.environ["LANGUAGE"] = "en_US.UTF-8" | ||||||
|  |  | ||||||
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | ||||||
| from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, su_to_zulip | from scripts.lib.zulip_tools import DEPLOYMENTS_DIR, FAIL, WARNING, ENDC, su_to_zulip, script_should_be_root | ||||||
|  |  | ||||||
|  | script_should_be_root() | ||||||
|  |  | ||||||
| logging.Formatter.converter = time.gmtime | logging.Formatter.converter = time.gmtime | ||||||
| logging.basicConfig(format="%(asctime)s upgrade-zulip-stage-2: %(message)s", | logging.basicConfig(format="%(asctime)s upgrade-zulip-stage-2: %(message)s", | ||||||
|                     level=logging.INFO) |                     level=logging.INFO) | ||||||
|  |  | ||||||
| if os.getuid() != 0: |  | ||||||
|     logging.error("Must be run as root.") |  | ||||||
|     sys.exit(1) |  | ||||||
|  |  | ||||||
| # make sure we have appropriate file permissions | # make sure we have appropriate file permissions | ||||||
| os.umask(0o22) | os.umask(0o22) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -359,3 +359,30 @@ def file_or_package_hash_updated(paths, hash_name, is_force, package_versions=[] | |||||||
|             hash_file.write(new_hash) |             hash_file.write(new_hash) | ||||||
|             return True |             return True | ||||||
|     return False |     return False | ||||||
|  |  | ||||||
|  | def is_root() -> bool: | ||||||
|  |     if 'posix' in os.name and os.geteuid() == 0: | ||||||
|  |         return True | ||||||
|  |     return False | ||||||
|  |  | ||||||
|  | def script_should_not_be_root() -> None: | ||||||
|  |     script_name = os.path.abspath(sys.argv[0]) | ||||||
|  |     if is_root(): | ||||||
|  |         msg = ("{shortname} should not be run as root. Use `su zulip` to switch to the 'zulip'\n" | ||||||
|  |                "user before rerunning this, or use \n  su zulip -c '{name} ...'\n" | ||||||
|  |                "to switch users and run this as a single command.").format( | ||||||
|  |             name=script_name, | ||||||
|  |             shortname=os.path.basename(script_name)) | ||||||
|  |         print(msg) | ||||||
|  |         sys.exit(1) | ||||||
|  |  | ||||||
|  | def script_should_be_root(strip_lib_from_paths: bool=False) -> None: | ||||||
|  |     script_name = os.path.abspath(sys.argv[0]) | ||||||
|  |     # Since these Python scripts are run inside a thin shell wrapper, | ||||||
|  |     # we need to replace the paths in order to ensure we instruct | ||||||
|  |     # users to (re)run the right command. | ||||||
|  |     if strip_lib_from_paths: | ||||||
|  |         script_name = script_name.replace("scripts/lib/upgrade", "scripts/upgrade") | ||||||
|  |     if not is_root(): | ||||||
|  |         print("{} must be run as root.".format(script_name)) | ||||||
|  |         sys.exit(1) | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ set -e | |||||||
|  |  | ||||||
| usage() { | usage() { | ||||||
|     cat <<EOF >&2 |     cat <<EOF >&2 | ||||||
| Usage: $0 --hostname=zulip.example.com --email=admin@example.com [--method={webroot|standalone}] [--no-zulip-conf] | Usage: $0 --email=admin@example.com [--method={webroot|standalone}] \ | ||||||
|  | [--no-zulip-conf] hostname.example.com [another.example.com] | ||||||
| EOF | EOF | ||||||
|     exit 1 |     exit 1 | ||||||
| } | } | ||||||
| @@ -15,15 +16,10 @@ if [ "$EUID" -ne 0 ]; then | |||||||
| fi | fi | ||||||
|  |  | ||||||
| method=webroot | method=webroot | ||||||
| args="$(getopt -o '' --long help,hostname:,email:,method:,deploy-hook:,no-zulip-conf,agree-tos -n "$0" -- "$@")" | args="$(getopt -o '' --long help,email:,method:,deploy-hook:,no-zulip-conf,agree-tos -n "$0" -- "$@")" | ||||||
| eval "set -- $args" | eval "set -- $args" | ||||||
| while true; do | while true; do | ||||||
|     case "$1" in |     case "$1" in | ||||||
|         --hostname) |  | ||||||
|             DOMAIN="$2" |  | ||||||
|             shift |  | ||||||
|             shift |  | ||||||
|             ;; |  | ||||||
|         --email) |         --email) | ||||||
|             EMAIL="$2" |             EMAIL="$2" | ||||||
|             shift |             shift | ||||||
| @@ -52,11 +48,19 @@ while true; do | |||||||
|             shift |             shift | ||||||
|             ;; |             ;; | ||||||
|         --) |         --) | ||||||
|  |             shift | ||||||
|             break |             break | ||||||
|             ;; |             ;; | ||||||
|     esac |     esac | ||||||
| done | done | ||||||
|  |  | ||||||
|  | # Parse the remaining arguments as Subject Alternative Names to pass to certbot | ||||||
|  | HOSTNAMES=() | ||||||
|  | for arg; do | ||||||
|  |     HOSTNAMES+=(-d "$arg") | ||||||
|  | done | ||||||
|  | DOMAIN=$1 | ||||||
|  |  | ||||||
| if [ -n "$show_help" ]; then | if [ -n "$show_help" ]; then | ||||||
|     usage |     usage | ||||||
| fi | fi | ||||||
| @@ -94,7 +98,7 @@ chmod a+x "$CERTBOT_PATH" | |||||||
| # Passing --force-interactive suppresses a warning, but also brings up | # Passing --force-interactive suppresses a warning, but also brings up | ||||||
| # an annoying prompt we stifle with --no-eff-email. | # an annoying prompt we stifle with --no-eff-email. | ||||||
| "$CERTBOT_PATH" certonly "${method_args[@]}" \ | "$CERTBOT_PATH" certonly "${method_args[@]}" \ | ||||||
|                 -d "$DOMAIN" -m "$EMAIL" \ |                 "${HOSTNAMES[@]}" -m "$EMAIL" \ | ||||||
|                 $agree_tos --force-renewal \ |                 $agree_tos --force-renewal \ | ||||||
|                 "${deploy_hook[@]}" \ |                 "${deploy_hook[@]}" \ | ||||||
|                 --force-interactive --no-eff-email |                 --force-interactive --no-eff-email | ||||||
|   | |||||||
| @@ -5,7 +5,9 @@ import sys | |||||||
| import subprocess | import subprocess | ||||||
| import configparser | import configparser | ||||||
| import re | import re | ||||||
| from lib.zulip_tools import parse_lsb_release | from lib.zulip_tools import parse_lsb_release, script_should_be_root | ||||||
|  |  | ||||||
|  | script_should_be_root() | ||||||
|  |  | ||||||
| force = False | force = False | ||||||
| extra_args = sys.argv[1:] | extra_args = sys.argv[1:] | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: PACKAGE VERSION\n" | "Project-Id-Version: PACKAGE VERSION\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-11-07 15:19+0000\n" | "POT-Creation-Date: 2018-11-28 20:32+0000\n" | ||||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||||
| @@ -1393,9 +1393,10 @@ msgstr "" | |||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_new_email.html:29 | #: templates/zerver/emails/compiled/confirm_new_email.html:29 | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:27 | #: templates/zerver/emails/compiled/confirm_registration.html:27 | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:56 | #: templates/zerver/emails/compiled/followup_day1.html:55 | ||||||
| #: templates/zerver/emails/compiled/invitation.html:26 | #: templates/zerver/emails/compiled/invitation.html:26 | ||||||
| #: templates/zerver/emails/compiled/invitation_reminder.html:22 | #: templates/zerver/emails/compiled/invitation_reminder.html:22 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:36 | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:28 | #: templates/zerver/emails/confirm_new_email.source.html:28 | ||||||
| #: templates/zerver/emails/confirm_new_email.txt:15 | #: templates/zerver/emails/confirm_new_email.txt:15 | ||||||
| #: templates/zerver/emails/confirm_registration.source.html:26 | #: templates/zerver/emails/confirm_registration.source.html:26 | ||||||
| @@ -1411,8 +1412,9 @@ msgstr "" | |||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_new_email.html:30 | #: templates/zerver/emails/compiled/confirm_new_email.html:30 | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:28 | #: templates/zerver/emails/compiled/confirm_registration.html:28 | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:57 | #: templates/zerver/emails/compiled/followup_day1.html:56 | ||||||
| #: templates/zerver/emails/compiled/notify_change_in_email.html:15 | #: templates/zerver/emails/compiled/notify_change_in_email.html:15 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:37 | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:29 | #: templates/zerver/emails/confirm_new_email.source.html:29 | ||||||
| #: templates/zerver/emails/confirm_new_email.txt:16 | #: templates/zerver/emails/confirm_new_email.txt:16 | ||||||
| #: templates/zerver/emails/confirm_registration.source.html:27 | #: templates/zerver/emails/confirm_registration.source.html:27 | ||||||
| @@ -1452,6 +1454,7 @@ msgid "Complete registration" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:20 | #: templates/zerver/emails/compiled/confirm_registration.html:20 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:29 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1500,88 +1503,80 @@ msgid "Thanks for using Zulip!" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:9 | #: templates/zerver/emails/compiled/followup_day1.html:9 | ||||||
| #: templates/zerver/emails/compiled/invitation.html:9 | msgid "Welcome to Zulip!" | ||||||
| #: templates/zerver/emails/followup_day1.source.html:8 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:1 |  | ||||||
| #: templates/zerver/emails/invitation.source.html:8 |  | ||||||
| #: templates/zerver/emails/invitation.txt:1 |  | ||||||
| msgid "Hi there," |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:11 | #: templates/zerver/emails/compiled/followup_day1.html:13 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:10 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:3 |  | ||||||
| msgid "Welcome to Zulip! A few tips to get you started:" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:14 |  | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Zulip works best when it's always open, so we suggest downloading\n" | "        You've created the new Zulip organization <b>%(realm_name)s</b>.\n" | ||||||
| "    our <a href=\"https://zulipchat.com/apps\" style=\"color:hsl(164, 42%%, " | "        " | ||||||
| "47%%); text-decoration:underline\">desktop and mobile apps</a>.\n" |  | ||||||
| "    " |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:19 | #: templates/zerver/emails/compiled/followup_day1.html:17 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:18 | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    To access your account from the apps, enter this Organization URL:\n" | "        You've joined the Zulip organization <b>%(realm_name)s</b>.\n" | ||||||
| "    " | "        " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:24 | ||||||
|  | msgid "Your account details:" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:26 | ||||||
|  | msgid "Organization URL:" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:29 | #: templates/zerver/emails/compiled/followup_day1.html:29 | ||||||
| #, python-format | msgid "Use your LDAP account to login" | ||||||
| msgid "" |  | ||||||
| "\n" |  | ||||||
| "    Become a Zulip pro with a few\n" |  | ||||||
| "    <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, " |  | ||||||
| "47%%); text-decoration:underline\">keyboard shortcuts</a>:\n" |  | ||||||
| "    " |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:34 | #: templates/zerver/emails/compiled/followup_day1.html:32 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:33 | msgid "Email:" | ||||||
| #: templates/zerver/emails/followup_day1.txt:17 |  | ||||||
| msgid "Next unread thread" |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:35 | #: templates/zerver/emails/compiled/followup_day1.html:35 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:34 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:18 |  | ||||||
| msgid "Reply to message under the blue box" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:36 |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:35 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:19 |  | ||||||
| msgid "Start a new topic" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:40 |  | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Give our <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, " | "    (you'll need these to sign in to the <a href=\"https://zulipchat.com/apps" | ||||||
| "42%%, 47%%); text-decoration:underline\">guide for new\n" | "\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">mobile " | ||||||
| "    %(user_role_group)s</a> a spin.\n" | "and desktop</a> apps)\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:42 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "        Check out our <a href=\"%(getting_started_link)s\" style=\"color:" | ||||||
|  | "hsl(164, 42%%, 47%%); text-decoration:underline\">guide for admins</a>, " | ||||||
|  | "become a Zulip pro with a\n" | ||||||
|  | "        few <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, " | ||||||
|  | "42%%, 47%%); text-decoration:underline\">keyboard shortcuts</a>, or <a href=" | ||||||
|  | "\"%(realm_uri)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:" | ||||||
|  | "underline\">dive right in</a>!\n" | ||||||
|  | "        " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:47 | #: templates/zerver/emails/compiled/followup_day1.html:47 | ||||||
|  | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Zulip combines the real-time ease of chat with the threaded " | "        <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, " | ||||||
| "organization\n" | "47%%); text-decoration:underline\">Learn more</a> about Zulip, become a pro " | ||||||
| "    of email. Zulip is about productivity—making communication fun and\n" | "with a few\n" | ||||||
| "    easy, while avoiding the distracting and disorganized conversations of\n" | "        <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, " | ||||||
| "    chatrooms. We hope you love using Zulip as much as we do.\n" | "47%%); text-decoration:underline\">keyboard shortcuts</a>, or <a href=" | ||||||
| "    " | "\"%(realm_uri)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:" | ||||||
|  | "underline\">dive right in</a>!\n" | ||||||
|  | "        " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:61 | #: templates/zerver/emails/compiled/followup_day1.html:60 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1595,6 +1590,14 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/invitation.html:9 | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:8 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:1 | ||||||
|  | #: templates/zerver/emails/invitation.source.html:8 | ||||||
|  | #: templates/zerver/emails/invitation.txt:1 | ||||||
|  | msgid "Hi there," | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/invitation.html:12 | #: templates/zerver/emails/compiled/invitation.html:12 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1675,6 +1678,38 @@ msgstr "" | |||||||
| msgid "Best," | msgid "Best," | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:10 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    Dear former administrators of %(realm_name)s,\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:15 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    One of your administrators requested reactivation of the\n" | ||||||
|  | "    previously deactivated Zulip organization hosted at %(realm_uri)s.  If " | ||||||
|  | "you'd\n" | ||||||
|  | "    like to do confirm that request and reactivate the organization, please " | ||||||
|  | "click here:\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:21 | ||||||
|  | msgid "Reactivate organization" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:23 | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    If the request was in error, you can take no action and this link\n" | ||||||
|  | "    will expire in 24 hours.\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:21 | #: templates/zerver/emails/confirm_new_email.source.html:21 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1719,6 +1754,11 @@ msgid "" | |||||||
| "questions." | "questions." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:10 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:3 | ||||||
|  | msgid "Welcome to Zulip! A few tips to get you started:" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:13 | #: templates/zerver/emails/followup_day1.source.html:13 | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1727,6 +1767,13 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:18 | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    To access your account from the apps, enter this Organization URL:\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:28 | #: templates/zerver/emails/followup_day1.source.html:28 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1736,6 +1783,21 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:33 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:17 | ||||||
|  | msgid "Next unread thread" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:34 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:18 | ||||||
|  | msgid "Reply to message under the blue box" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:35 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:19 | ||||||
|  | msgid "Start a new topic" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:39 | #: templates/zerver/emails/followup_day1.source.html:39 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -3324,43 +3386,43 @@ msgstr "" | |||||||
| msgid "Wrong subdomain" | msgid "Wrong subdomain" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:716 zerver/views/auth.py:747 | #: zerver/views/auth.py:717 zerver/views/auth.py:748 | ||||||
| msgid "Dev environment not enabled." | msgid "Dev environment not enabled." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:732 zerver/views/auth.py:780 | #: zerver/views/auth.py:733 zerver/views/auth.py:781 | ||||||
| msgid "This organization has been deactivated." | msgid "This organization has been deactivated." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:735 zerver/views/auth.py:777 | #: zerver/views/auth.py:736 zerver/views/auth.py:778 | ||||||
| msgid "Your account has been disabled." | msgid "Your account has been disabled." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:738 | #: zerver/views/auth.py:739 | ||||||
| msgid "This user is not registered." | msgid "This user is not registered." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:783 | #: zerver/views/auth.py:784 | ||||||
| msgid "Password auth is disabled in your team." | msgid "Password auth is disabled in your team." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:789 | #: zerver/views/auth.py:790 | ||||||
| msgid "This user is not registered; do so from a browser." | msgid "This user is not registered; do so from a browser." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:791 zerver/views/auth.py:875 | #: zerver/views/auth.py:792 zerver/views/auth.py:876 | ||||||
| msgid "Your username or password is incorrect." | msgid "Your username or password is incorrect." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:816 | #: zerver/views/auth.py:817 | ||||||
| msgid "Invalid subdomain" | msgid "Invalid subdomain" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:822 | #: zerver/views/auth.py:823 | ||||||
| msgid "Subdomain required" | msgid "Subdomain required" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:883 | #: zerver/views/auth.py:884 | ||||||
| msgid "GOOGLE_CLIENT_ID is not configured" | msgid "GOOGLE_CLIENT_ID is not configured" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,11 +2,11 @@ | |||||||
|   "\"__file_name__\" was too large; the maximum file size is 25MiB.": "\"__file_name__\" 이 너무 큽니다.; 최대 파일크기는 25MiB입니다.", |   "\"__file_name__\" was too large; the maximum file size is 25MiB.": "\"__file_name__\" 이 너무 큽니다.; 최대 파일크기는 25MiB입니다.", | ||||||
|   "(This user has been deactivated)": "(이 사용자는 비활성화되었습니다.)", |   "(This user has been deactivated)": "(이 사용자는 비활성화되었습니다.)", | ||||||
|   "(no topic)": "(주제없음)", |   "(no topic)": "(주제없음)", | ||||||
|   "1 day": "", |   "1 day": "1 일", | ||||||
|   "1 hour": "", |   "1 hour": "1 시간", | ||||||
|   "1 week": "", |   "1 week": "1 주", | ||||||
|   "10 minutes": "", |   "10 minutes": "10 분", | ||||||
|   "2 minutes": "", |   "2 minutes": "2 분", | ||||||
|   "24-hour time (17:00 instead of 5:00 PM)": "24 시간 표시 (5:00 PM 대신 17:00)", |   "24-hour time (17:00 instead of 5:00 PM)": "24 시간 표시 (5:00 PM 대신 17:00)", | ||||||
|   "<b>Private, protected history:</b> must be invited by a member; new members can only see messages sent after they join; hidden from non-administrator users": "", |   "<b>Private, protected history:</b> must be invited by a member; new members can only see messages sent after they join; hidden from non-administrator users": "", | ||||||
|   "<b>Private, shared history:</b> must be invited by a member; new members can view complete message history; hidden from non-administrator users": "", |   "<b>Private, shared history:</b> must be invited by a member; new members can view complete message history; hidden from non-administrator users": "", | ||||||
| @@ -39,13 +39,13 @@ | |||||||
|   "Add member...": "회원 추가 ...", |   "Add member...": "회원 추가 ...", | ||||||
|   "Add members of your organization to mentionable user groups.": "관심있는 사용자 그룹에 단체 구성원을 추가하십시오.", |   "Add members of your organization to mentionable user groups.": "관심있는 사용자 그룹에 단체 구성원을 추가하십시오.", | ||||||
|   "Add new default stream": "새 기본 스트림 추가", |   "Add new default stream": "새 기본 스트림 추가", | ||||||
|   "Add option": "", |   "Add option": "옵션 추가", | ||||||
|   "Add profile field": "", |   "Add profile field": "", | ||||||
|   "Add question": "", |   "Add question": "질문 추가", | ||||||
|   "Add stream": "스트림 추가", |   "Add stream": "스트림 추가", | ||||||
|   "Add task": "", |   "Add task": "업무 추가", | ||||||
|   "Added successfully!": "성공적으로 추가됨!", |   "Added successfully!": "성공적으로 추가됨!", | ||||||
|   "Administrator": "", |   "Administrator": "관리자", | ||||||
|   "Administrators can always delete any message.": "관리자는 언제든지 모든 메시지를 삭제할 수 있습니다.", |   "Administrators can always delete any message.": "관리자는 언제든지 모든 메시지를 삭제할 수 있습니다.", | ||||||
|   "Admins only": "관리자 만", |   "Admins only": "관리자 만", | ||||||
|   "Alert word": "경고문", |   "Alert word": "경고문", | ||||||
| @@ -149,7 +149,7 @@ | |||||||
|   "Default language": "기본 언어", |   "Default language": "기본 언어", | ||||||
|   "Default streams": "기본 스트림", |   "Default streams": "기본 스트림", | ||||||
|   "Default user settings": "", |   "Default user settings": "", | ||||||
|   "Delete": "", |   "Delete": "삭제", | ||||||
|   "Delete alert word": "경고문 삭제", |   "Delete alert word": "경고문 삭제", | ||||||
|   "Delete avatar": "아바타 삭제", |   "Delete avatar": "아바타 삭제", | ||||||
|   "Delete bot": "봇 삭제", |   "Delete bot": "봇 삭제", | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ | |||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "it": { |   "it": { | ||||||
|     "not_translated": 129, |     "not_translated": 0, | ||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "ja": { |   "ja": { | ||||||
| @@ -52,7 +52,7 @@ | |||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "ko": { |   "ko": { | ||||||
|     "not_translated": 10, |     "not_translated": 9, | ||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "ml": { |   "ml": { | ||||||
| @@ -92,7 +92,7 @@ | |||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "zh_Hans": { |   "zh_Hans": { | ||||||
|     "not_translated": 8, |     "not_translated": 0, | ||||||
|     "total": 140 |     "total": 140 | ||||||
|   }, |   }, | ||||||
|   "zh_Hant": { |   "zh_Hant": { | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ msgstr "" | |||||||
| "Project-Id-Version: Zulip\n" | "Project-Id-Version: Zulip\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-11-03 00:32+0000\n" | "POT-Creation-Date: 2018-11-03 00:32+0000\n" | ||||||
| "PO-Revision-Date: 2018-11-05 14:34+0000\n" | "PO-Revision-Date: 2018-11-07 18:54+0000\n" | ||||||
| "Last-Translator: André Lopes Pereira <andrelopespereira@gmail.com>\n" | "Last-Translator: André Lopes Pereira <andrelopespereira@gmail.com>\n" | ||||||
| "Language-Team: Portuguese (http://www.transifex.com/zulip/zulip/language/pt/)\n" | "Language-Team: Portuguese (http://www.transifex.com/zulip/zulip/language/pt/)\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| @@ -842,7 +842,7 @@ msgstr "Documentação detalhada de atalhos de teclado" | |||||||
| #: templates/zerver/app/left_sidebar.html:5 | #: templates/zerver/app/left_sidebar.html:5 | ||||||
| #: templates/zerver/app/left_sidebar.html:10 | #: templates/zerver/app/left_sidebar.html:10 | ||||||
| msgid "All messages" | msgid "All messages" | ||||||
| msgstr "Todas mensagens" | msgstr "Todas as mensagens" | ||||||
|  |  | ||||||
| #: templates/zerver/app/left_sidebar.html:33 | #: templates/zerver/app/left_sidebar.html:33 | ||||||
| msgid "Starred messages" | msgid "Starred messages" | ||||||
|   | |||||||
| @@ -276,7 +276,7 @@ | |||||||
|   "Mark all messages in <b>__stream.name__</b> as read": "Marcar todas as mensagens em <b>__stream.name__</b> como lidas", |   "Mark all messages in <b>__stream.name__</b> as read": "Marcar todas as mensagens em <b>__stream.name__</b> como lidas", | ||||||
|   "Mark all messages in <b>__topic_name__</b> as read": "Marcar todas as mensagens em <b>__topic_name__</b> como lidas", |   "Mark all messages in <b>__topic_name__</b> as read": "Marcar todas as mensagens em <b>__topic_name__</b> como lidas", | ||||||
|   "Marketing team": "Time de marketing", |   "Marketing team": "Time de marketing", | ||||||
|   "Marking all messages as read\u2026": "Marcando todas as mensagens como lidas...", |   "Marking all messages as read\u2026": "Marcando todas as mensagens como lidas\\\\u2026", | ||||||
|   "Member": "Membro", |   "Member": "Membro", | ||||||
|   "Members and admins": "Membros e administradores", |   "Members and admins": "Membros e administradores", | ||||||
|   "Members and admins, but only admins can add generic bots": "Membros e administradores, mas apenas administradores podem adicionar bots genéricos", |   "Members and admins, but only admins can add generic bots": "Membros e administradores, mas apenas administradores podem adicionar bots genéricos", | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| # Sergey Korablin <s.korablin@gmail.com>, 2018 | # Sergey Korablin <s.korablin@gmail.com>, 2018 | ||||||
| # Sergey Korablin <s.korablin@gmail.com>, 2018 | # Sergey Korablin <s.korablin@gmail.com>, 2018 | ||||||
| # Никита Радченко <aygolan@gmail.com>, 2016 | # Никита Радченко <aygolan@gmail.com>, 2016 | ||||||
| # Султонбек Ахмедов <cooltonbek@gmail.com>, 2018 | # Султонбек Ахмедов <davlaterra@ya.ru>, 2018 | ||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: Zulip\n" | "Project-Id-Version: Zulip\n" | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: Zulip\n" | "Project-Id-Version: Zulip\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-11-07 15:19+0000\n" | "POT-Creation-Date: 2018-11-28 20:32+0000\n" | ||||||
| "PO-Revision-Date: 2018-04-11 21:06+0000\n" | "PO-Revision-Date: 2018-04-11 21:06+0000\n" | ||||||
| "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" | "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" | ||||||
| "Language-Team: Tamil (http://www.transifex.com/zulip/zulip/language/ta/)\n" | "Language-Team: Tamil (http://www.transifex.com/zulip/zulip/language/ta/)\n" | ||||||
| @@ -1407,9 +1407,10 @@ msgstr "" | |||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_new_email.html:29 | #: templates/zerver/emails/compiled/confirm_new_email.html:29 | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:27 | #: templates/zerver/emails/compiled/confirm_registration.html:27 | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:56 | #: templates/zerver/emails/compiled/followup_day1.html:55 | ||||||
| #: templates/zerver/emails/compiled/invitation.html:26 | #: templates/zerver/emails/compiled/invitation.html:26 | ||||||
| #: templates/zerver/emails/compiled/invitation_reminder.html:22 | #: templates/zerver/emails/compiled/invitation_reminder.html:22 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:36 | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:28 | #: templates/zerver/emails/confirm_new_email.source.html:28 | ||||||
| #: templates/zerver/emails/confirm_new_email.txt:15 | #: templates/zerver/emails/confirm_new_email.txt:15 | ||||||
| #: templates/zerver/emails/confirm_registration.source.html:26 | #: templates/zerver/emails/confirm_registration.source.html:26 | ||||||
| @@ -1425,8 +1426,9 @@ msgstr "" | |||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_new_email.html:30 | #: templates/zerver/emails/compiled/confirm_new_email.html:30 | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:28 | #: templates/zerver/emails/compiled/confirm_registration.html:28 | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:57 | #: templates/zerver/emails/compiled/followup_day1.html:56 | ||||||
| #: templates/zerver/emails/compiled/notify_change_in_email.html:15 | #: templates/zerver/emails/compiled/notify_change_in_email.html:15 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:37 | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:29 | #: templates/zerver/emails/confirm_new_email.source.html:29 | ||||||
| #: templates/zerver/emails/confirm_new_email.txt:16 | #: templates/zerver/emails/confirm_new_email.txt:16 | ||||||
| #: templates/zerver/emails/confirm_registration.source.html:27 | #: templates/zerver/emails/confirm_registration.source.html:27 | ||||||
| @@ -1468,6 +1470,7 @@ msgid "Complete registration" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:20 | #: templates/zerver/emails/compiled/confirm_registration.html:20 | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:29 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1516,88 +1519,82 @@ msgid "Thanks for using Zulip!" | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:9 | #: templates/zerver/emails/compiled/followup_day1.html:9 | ||||||
| #: templates/zerver/emails/compiled/invitation.html:9 | msgid "Welcome to Zulip!" | ||||||
| #: templates/zerver/emails/followup_day1.source.html:8 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:1 |  | ||||||
| #: templates/zerver/emails/invitation.source.html:8 |  | ||||||
| #: templates/zerver/emails/invitation.txt:1 |  | ||||||
| msgid "Hi there," |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:11 | #: templates/zerver/emails/compiled/followup_day1.html:13 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:10 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:3 |  | ||||||
| msgid "Welcome to Zulip! A few tips to get you started:" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:14 |  | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Zulip works best when it's always open, so we suggest downloading\n" | "        You've created the new Zulip organization <b>%(realm_name)s</b>.\n" | ||||||
| "    our <a href=\"https://zulipchat.com/apps\" style=\"color:hsl(164, 42%%, " | "        " | ||||||
| "47%%); text-decoration:underline\">desktop and mobile apps</a>.\n" |  | ||||||
| "    " |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:19 | #: templates/zerver/emails/compiled/followup_day1.html:17 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:18 | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    To access your account from the apps, enter this Organization URL:\n" | "        You've joined the Zulip organization <b>%(realm_name)s</b>.\n" | ||||||
| "    " | "        " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:24 | ||||||
|  | msgid "Your account details:" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:26 | ||||||
|  | msgid "Organization URL:" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:29 | #: templates/zerver/emails/compiled/followup_day1.html:29 | ||||||
| #, python-format | msgid "Use your LDAP account to login" | ||||||
| msgid "" |  | ||||||
| "\n" |  | ||||||
| "    Become a Zulip pro with a few\n" |  | ||||||
| "    <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, " |  | ||||||
| "47%%); text-decoration:underline\">keyboard shortcuts</a>:\n" |  | ||||||
| "    " |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:34 | #: templates/zerver/emails/compiled/followup_day1.html:32 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:33 | #, fuzzy | ||||||
| #: templates/zerver/emails/followup_day1.txt:17 | #| msgid "Email" | ||||||
| msgid "Next unread thread" | msgid "Email:" | ||||||
| msgstr "" | msgstr "மின்னஞ்சல்" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:35 | #: templates/zerver/emails/compiled/followup_day1.html:35 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:34 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:18 |  | ||||||
| msgid "Reply to message under the blue box" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:36 |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:35 |  | ||||||
| #: templates/zerver/emails/followup_day1.txt:19 |  | ||||||
| msgid "Start a new topic" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:40 |  | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Give our <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, " | "    (you'll need these to sign in to the <a href=\"https://zulipchat.com/apps" | ||||||
| "42%%, 47%%); text-decoration:underline\">guide for new\n" | "\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">mobile " | ||||||
| "    %(user_role_group)s</a> a spin.\n" | "and desktop</a> apps)\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/followup_day1.html:42 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "        Check out our <a href=\"%(getting_started_link)s\" style=\"color:" | ||||||
|  | "hsl(164, 42%%, 47%%); text-decoration:underline\">guide for admins</a>, " | ||||||
|  | "become a Zulip pro with a\n" | ||||||
|  | "        few <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, " | ||||||
|  | "42%%, 47%%); text-decoration:underline\">keyboard shortcuts</a>, or <a href=" | ||||||
|  | "\"%(realm_uri)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:" | ||||||
|  | "underline\">dive right in</a>!\n" | ||||||
|  | "        " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:47 | #: templates/zerver/emails/compiled/followup_day1.html:47 | ||||||
|  | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| "    Zulip combines the real-time ease of chat with the threaded " | "        <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, " | ||||||
| "organization\n" | "47%%); text-decoration:underline\">Learn more</a> about Zulip, become a pro " | ||||||
| "    of email. Zulip is about productivity—making communication fun and\n" | "with a few\n" | ||||||
| "    easy, while avoiding the distracting and disorganized conversations of\n" | "        <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, " | ||||||
| "    chatrooms. We hope you love using Zulip as much as we do.\n" | "47%%); text-decoration:underline\">keyboard shortcuts</a>, or <a href=" | ||||||
| "    " | "\"%(realm_uri)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:" | ||||||
|  | "underline\">dive right in</a>!\n" | ||||||
|  | "        " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:61 | #: templates/zerver/emails/compiled/followup_day1.html:60 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1611,6 +1608,14 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/invitation.html:9 | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:8 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:1 | ||||||
|  | #: templates/zerver/emails/invitation.source.html:8 | ||||||
|  | #: templates/zerver/emails/invitation.txt:1 | ||||||
|  | msgid "Hi there," | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/invitation.html:12 | #: templates/zerver/emails/compiled/invitation.html:12 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1691,6 +1696,38 @@ msgstr "" | |||||||
| msgid "Best," | msgid "Best," | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:10 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    Dear former administrators of %(realm_name)s,\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:15 | ||||||
|  | #, python-format | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    One of your administrators requested reactivation of the\n" | ||||||
|  | "    previously deactivated Zulip organization hosted at %(realm_uri)s.  If " | ||||||
|  | "you'd\n" | ||||||
|  | "    like to do confirm that request and reactivate the organization, please " | ||||||
|  | "click here:\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:21 | ||||||
|  | msgid "Reactivate organization" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/compiled/realm_reactivation.html:23 | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    If the request was in error, you can take no action and this link\n" | ||||||
|  | "    will expire in 24 hours.\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/confirm_new_email.source.html:21 | #: templates/zerver/emails/confirm_new_email.source.html:21 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1735,6 +1772,11 @@ msgid "" | |||||||
| "questions." | "questions." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:10 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:3 | ||||||
|  | msgid "Welcome to Zulip! A few tips to get you started:" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:13 | #: templates/zerver/emails/followup_day1.source.html:13 | ||||||
| msgid "" | msgid "" | ||||||
| "\n" | "\n" | ||||||
| @@ -1743,6 +1785,13 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:18 | ||||||
|  | msgid "" | ||||||
|  | "\n" | ||||||
|  | "    To access your account from the apps, enter this Organization URL:\n" | ||||||
|  | "    " | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:28 | #: templates/zerver/emails/followup_day1.source.html:28 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1752,6 +1801,21 @@ msgid "" | |||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:33 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:17 | ||||||
|  | msgid "Next unread thread" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:34 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:18 | ||||||
|  | msgid "Reply to message under the blue box" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: templates/zerver/emails/followup_day1.source.html:35 | ||||||
|  | #: templates/zerver/emails/followup_day1.txt:19 | ||||||
|  | msgid "Start a new topic" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/followup_day1.source.html:39 | #: templates/zerver/emails/followup_day1.source.html:39 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "" | msgid "" | ||||||
| @@ -3342,43 +3406,43 @@ msgstr "" | |||||||
| msgid "Wrong subdomain" | msgid "Wrong subdomain" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:716 zerver/views/auth.py:747 | #: zerver/views/auth.py:717 zerver/views/auth.py:748 | ||||||
| msgid "Dev environment not enabled." | msgid "Dev environment not enabled." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:732 zerver/views/auth.py:780 | #: zerver/views/auth.py:733 zerver/views/auth.py:781 | ||||||
| msgid "This organization has been deactivated." | msgid "This organization has been deactivated." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:735 zerver/views/auth.py:777 | #: zerver/views/auth.py:736 zerver/views/auth.py:778 | ||||||
| msgid "Your account has been disabled." | msgid "Your account has been disabled." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:738 | #: zerver/views/auth.py:739 | ||||||
| msgid "This user is not registered." | msgid "This user is not registered." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:783 | #: zerver/views/auth.py:784 | ||||||
| msgid "Password auth is disabled in your team." | msgid "Password auth is disabled in your team." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:789 | #: zerver/views/auth.py:790 | ||||||
| msgid "This user is not registered; do so from a browser." | msgid "This user is not registered; do so from a browser." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:791 zerver/views/auth.py:875 | #: zerver/views/auth.py:792 zerver/views/auth.py:876 | ||||||
| msgid "Your username or password is incorrect." | msgid "Your username or password is incorrect." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:816 | #: zerver/views/auth.py:817 | ||||||
| msgid "Invalid subdomain" | msgid "Invalid subdomain" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:822 | #: zerver/views/auth.py:823 | ||||||
| msgid "Subdomain required" | msgid "Subdomain required" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: zerver/views/auth.py:883 | #: zerver/views/auth.py:884 | ||||||
| msgid "GOOGLE_CLIENT_ID is not configured" | msgid "GOOGLE_CLIENT_ID is not configured" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ msgstr "" | |||||||
| "Project-Id-Version: Zulip\n" | "Project-Id-Version: Zulip\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2018-11-03 00:32+0000\n" | "POT-Creation-Date: 2018-11-03 00:32+0000\n" | ||||||
| "PO-Revision-Date: 2018-11-03 00:04+0000\n" | "PO-Revision-Date: 2018-11-08 07:30+0000\n" | ||||||
| "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" | "Last-Translator: longjiang li <cqlilon@live.com>\n" | ||||||
| "Language-Team: Chinese Simplified (http://www.transifex.com/zulip/zulip/language/zh-Hans/)\n" | "Language-Team: Chinese Simplified (http://www.transifex.com/zulip/zulip/language/zh-Hans/)\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| "Content-Type: text/plain; charset=UTF-8\n" | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
| @@ -318,7 +318,7 @@ msgstr "保存为草稿" | |||||||
|  |  | ||||||
| #: templates/zerver/app/compose.html:20 | #: templates/zerver/app/compose.html:20 | ||||||
| msgid "New message" | msgid "New message" | ||||||
| msgstr "" | msgstr "新消息" | ||||||
|  |  | ||||||
| #: templates/zerver/app/compose.html:27 templates/zerver/app/compose.html:28 | #: templates/zerver/app/compose.html:27 templates/zerver/app/compose.html:28 | ||||||
| msgid "New topic" | msgid "New topic" | ||||||
| @@ -1380,7 +1380,7 @@ msgid "" | |||||||
| "    If you did not request this change, please contact us immediately at\n" | "    If you did not request this change, please contact us immediately at\n" | ||||||
| "    <a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>.\n" | "    <a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>.\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n如果您没有发送修改请求,请立即联系我们<a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/confirm_new_email.html:29 | #: templates/zerver/emails/compiled/confirm_new_email.html:29 | ||||||
| #: templates/zerver/emails/compiled/confirm_registration.html:27 | #: templates/zerver/emails/compiled/confirm_registration.html:27 | ||||||
| @@ -1450,7 +1450,7 @@ msgid "" | |||||||
| "    <a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>,\n" | "    <a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>,\n" | ||||||
| "    if you have any questions.\n" | "    if you have any questions.\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n如果您有任何问题,都可以告诉我们<a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/find_team.html:9 | #: templates/zerver/emails/compiled/find_team.html:9 | ||||||
| #: templates/zerver/emails/find_team.source.html:8 | #: templates/zerver/emails/find_team.source.html:8 | ||||||
| @@ -1511,7 +1511,7 @@ msgid "" | |||||||
| "    Zulip works best when it's always open, so we suggest downloading\n" | "    Zulip works best when it's always open, so we suggest downloading\n" | ||||||
| "    our <a href=\"https://zulipchat.com/apps\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">desktop and mobile apps</a>.\n" | "    our <a href=\"https://zulipchat.com/apps\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">desktop and mobile apps</a>.\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n为了使用Zulip时获得更好的体验,建议下载使用<a href=\"https://zulipchat.com/apps\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">桌面端和移动端App</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:19 | #: templates/zerver/emails/compiled/followup_day1.html:19 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:18 | #: templates/zerver/emails/followup_day1.source.html:18 | ||||||
| @@ -1528,7 +1528,7 @@ msgid "" | |||||||
| "    Become a Zulip pro with a few\n" | "    Become a Zulip pro with a few\n" | ||||||
| "    <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">keyboard shortcuts</a>:\n" | "    <a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">keyboard shortcuts</a>:\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n为了更方便的使用Zulip,了解一下<a href=\"%(keyboard_shortcuts_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">快捷键</a>:" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:34 | #: templates/zerver/emails/compiled/followup_day1.html:34 | ||||||
| #: templates/zerver/emails/followup_day1.source.html:33 | #: templates/zerver/emails/followup_day1.source.html:33 | ||||||
| @@ -1555,7 +1555,7 @@ msgid "" | |||||||
| "    Give our <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">guide for new\n" | "    Give our <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">guide for new\n" | ||||||
| "    %(user_role_group)s</a> a spin.\n" | "    %(user_role_group)s</a> a spin.\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n快速浏览<a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(user_role_group)s新手教程</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/followup_day1.html:47 | #: templates/zerver/emails/compiled/followup_day1.html:47 | ||||||
| msgid "" | msgid "" | ||||||
| @@ -1576,7 +1576,7 @@ msgid "" | |||||||
| "    chat with us live on the\n" | "    chat with us live on the\n" | ||||||
| "    <a href=\"https://chat.zulip.org\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">Zulip community server</a>!\n" | "    <a href=\"https://chat.zulip.org\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">Zulip community server</a>!\n" | ||||||
| "    " | "    " | ||||||
| msgstr "" | msgstr "\n例如:关注我们的<a href=\"https://twitter.com/zulip\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">Twitter</a>,给我们的<a href=\"https://github.com/zulip/zulip\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">GitHub</a>加星,或者在<a href=\"https://chat.zulip.org\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">Zulip community server</a>和我们在线交流" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/invitation.html:12 | #: templates/zerver/emails/compiled/invitation.html:12 | ||||||
| #, python-format | #, python-format | ||||||
| @@ -1601,7 +1601,7 @@ msgid "" | |||||||
| "Feel free to give us a shout at <a href=\"mailto:%(support_email)s\" " | "Feel free to give us a shout at <a href=\"mailto:%(support_email)s\" " | ||||||
| "style=\"color:hsl(164, 42%%, 47%%); text-" | "style=\"color:hsl(164, 42%%, 47%%); text-" | ||||||
| "decoration:underline\">%(support_email)s</a>, if you have any questions." | "decoration:underline\">%(support_email)s</a>, if you have any questions." | ||||||
| msgstr "" | msgstr "如果您有任何问题,都可以告诉我们<a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/invitation.html:27 | #: templates/zerver/emails/compiled/invitation.html:27 | ||||||
| #: templates/zerver/emails/compiled/invitation_reminder.html:23 | #: templates/zerver/emails/compiled/invitation_reminder.html:23 | ||||||
| @@ -1625,7 +1625,7 @@ msgid "" | |||||||
| "href=\"mailto:%(referrer_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | "href=\"mailto:%(referrer_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | ||||||
| "decoration:underline\">%(referrer_email)s</a>) wants you to join them on " | "decoration:underline\">%(referrer_email)s</a>) wants you to join them on " | ||||||
| "Zulip, a workplace chat tool that actually makes you more productive." | "Zulip, a workplace chat tool that actually makes you more productive." | ||||||
| msgstr "" | msgstr "友情提示:%(referrer_name)s(<a href=\"mailto:%(referrer_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(referrer_email)s</a>)邀请你加入他们的Zulip,一个能大大提高工作效率的交流工具" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/invitation_reminder.html:19 | #: templates/zerver/emails/compiled/invitation_reminder.html:19 | ||||||
| #, python-format | #, python-format | ||||||
| @@ -1633,7 +1633,7 @@ msgid "" | |||||||
| "We're here for you at <a href=\"mailto:%(support_email)s\" " | "We're here for you at <a href=\"mailto:%(support_email)s\" " | ||||||
| "style=\"color:hsl(164, 42%%, 47%%); text-" | "style=\"color:hsl(164, 42%%, 47%%); text-" | ||||||
| "decoration:underline\">%(support_email)s</a> if you have any questions." | "decoration:underline\">%(support_email)s</a> if you have any questions." | ||||||
| msgstr "" | msgstr "如果您有任何问题请联系我们<a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/notify_change_in_email.html:9 | #: templates/zerver/emails/compiled/notify_change_in_email.html:9 | ||||||
| #: templates/zerver/emails/notify_change_in_email.source.html:8 | #: templates/zerver/emails/notify_change_in_email.source.html:8 | ||||||
| @@ -1651,7 +1651,7 @@ msgid "" | |||||||
| "change, please contact us immediately at <a " | "change, please contact us immediately at <a " | ||||||
| "href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | "href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | ||||||
| "decoration:underline\">%(support_email)s</a>." | "decoration:underline\">%(support_email)s</a>." | ||||||
| msgstr "" | msgstr "您Zulip账户关联的电子邮件变更为<a href=\"%(new_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(new_email)s</a>。如果不是您请求的变更请立即联系我们<a href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">%(support_email)s</a>" | ||||||
|  |  | ||||||
| #: templates/zerver/emails/compiled/notify_change_in_email.html:14 | #: templates/zerver/emails/compiled/notify_change_in_email.html:14 | ||||||
| #: templates/zerver/emails/notify_change_in_email.source.html:13 | #: templates/zerver/emails/notify_change_in_email.source.html:13 | ||||||
| @@ -2371,7 +2371,7 @@ msgstr "必须是组织管理员" | |||||||
|  |  | ||||||
| #: zerver/decorator.py:142 | #: zerver/decorator.py:142 | ||||||
| msgid "Must be a billing administrator or an organization administrator" | msgid "Must be a billing administrator or an organization administrator" | ||||||
| msgstr "" | msgstr "必须是账单管理员或者组织管理员" | ||||||
|  |  | ||||||
| #: zerver/decorator.py:224 | #: zerver/decorator.py:224 | ||||||
| msgid "Invalid subdomain for push notifications bouncer" | msgid "Invalid subdomain for push notifications bouncer" | ||||||
| @@ -3011,7 +3011,7 @@ msgstr "名称中有无效字符!" | |||||||
|  |  | ||||||
| #: zerver/lib/users.py:39 | #: zerver/lib/users.py:39 | ||||||
| msgid "Name is already in use!" | msgid "Name is already in use!" | ||||||
| msgstr "" | msgstr "用户名已被占用" | ||||||
|  |  | ||||||
| #: zerver/lib/users.py:44 zerver/views/users.py:283 zerver/views/users.py:455 | #: zerver/lib/users.py:44 zerver/views/users.py:283 zerver/views/users.py:455 | ||||||
| msgid "Bad name or username" | msgid "Bad name or username" | ||||||
| @@ -3736,7 +3736,7 @@ msgstr "无法停用唯一的社群管理员" | |||||||
|  |  | ||||||
| #: zerver/views/users.py:97 | #: zerver/views/users.py:97 | ||||||
| msgid "Guests cannot be organization administrators" | msgid "Guests cannot be organization administrators" | ||||||
| msgstr "" | msgstr "访客不能是组织管理员" | ||||||
|  |  | ||||||
| #: zerver/views/users.py:101 | #: zerver/views/users.py:101 | ||||||
| msgid "Cannot remove the only organization administrator" | msgid "Cannot remove the only organization administrator" | ||||||
|   | |||||||
| @@ -292,14 +292,14 @@ | |||||||
|   "Mobile notifications": "移动端通知", |   "Mobile notifications": "移动端通知", | ||||||
|   "Mobile notifications always (even when online)": "移动端通知(在线)", |   "Mobile notifications always (even when online)": "移动端通知(在线)", | ||||||
|   "Mobile notifications when offline": "离线时移动端通知", |   "Mobile notifications when offline": "离线时移动端通知", | ||||||
|   "Mobile push notifications are not configured on this server.": "", |   "Mobile push notifications are not configured on this server.": "服务器未配置移动端推送通知", | ||||||
|   "More than 2 weeks ago": "超过2周", |   "More than 2 weeks ago": "超过2周", | ||||||
|   "Mute stream": "静音频道", |   "Mute stream": "静音频道", | ||||||
|   "Mute the stream <b>__stream.name__</b>": "频道"<b>__stream.name__</b>"开启免打扰", |   "Mute the stream <b>__stream.name__</b>": "频道"<b>__stream.name__</b>"开启免打扰", | ||||||
|   "Mute the topic <b>__subject__</b>": "话题"<b>__subject__</b>"开启免打扰", |   "Mute the topic <b>__subject__</b>": "话题"<b>__subject__</b>"开启免打扰", | ||||||
|   "Mute the topic <b>__topic_name__</b>": "话题"<b>__topic_name__</b>"开启免打扰", |   "Mute the topic <b>__topic_name__</b>": "话题"<b>__topic_name__</b>"开启免打扰", | ||||||
|   "Mute topic": "静音主题", |   "Mute topic": "静音主题", | ||||||
|   "Muted streams don't show up in \\\"All messages\\\" or generate notifications unless you are mentioned.": "", |   "Muted streams don't show up in \\\"All messages\\\" or generate notifications unless you are mentioned.": "开启免打扰频道不会出现在\\\"所有信息 \"\\中,也不会产生通知,除非您被@提醒。", | ||||||
|   "Muted topics": "已静音主题", |   "Muted topics": "已静音主题", | ||||||
|   "N": "N", |   "N": "N", | ||||||
|   "Name": "名称", |   "Name": "名称", | ||||||
| @@ -316,14 +316,14 @@ | |||||||
|   "New conversation": "新对话", |   "New conversation": "新对话", | ||||||
|   "New email": "新电子邮件", |   "New email": "新电子邮件", | ||||||
|   "New full name": "新全名", |   "New full name": "新全名", | ||||||
|   "New members can only see messages sent after they join.": "", |   "New members can only see messages sent after they join.": "新成员只能看到加入后发送的消息。", | ||||||
|   "New members can view complete message history.": "", |   "New members can view complete message history.": "新成员可以查看完整的消息历史。", | ||||||
|   "New password": "新密码", |   "New password": "新密码", | ||||||
|   "New password is too weak": "", |   "New password is too weak": "新密码太弱", | ||||||
|   "New private message": "写私信", |   "New private message": "写私信", | ||||||
|   "New stream message": "写消息", |   "New stream message": "写消息", | ||||||
|   "New stream notifications:": "", |   "New stream notifications:": "新频道通知:", | ||||||
|   "New task": "", |   "New task": "新任务", | ||||||
|   "New topic": "新话题", |   "New topic": "新话题", | ||||||
|   "New user notifications:": "新用户通知", |   "New user notifications:": "新用户通知", | ||||||
|   "Next week": "下周", |   "Next week": "下周", | ||||||
| @@ -334,7 +334,7 @@ | |||||||
|   "No default streams match you current filter.": "没有默认频道可以匹配你当前的过滤器。", |   "No default streams match you current filter.": "没有默认频道可以匹配你当前的过滤器。", | ||||||
|   "No description.": "没有描述信息。", |   "No description.": "没有描述信息。", | ||||||
|   "No drafts.": "没有草稿", |   "No drafts.": "没有草稿", | ||||||
|   "No invites match your current filter.": "", |   "No invites match your current filter.": "没有邀请匹配当前过滤器。", | ||||||
|   "No more topics.": "没有更多话题。", |   "No more topics.": "没有更多话题。", | ||||||
|   "No restrictions": "无限制", |   "No restrictions": "无限制", | ||||||
|   "No users match your current filter.": "没有匹配到用户在你的筛选器中。", |   "No users match your current filter.": "没有匹配到用户在你的筛选器中。", | ||||||
| @@ -346,25 +346,25 @@ | |||||||
|   "Notifications stream changed!": "频道通知已更改!", |   "Notifications stream changed!": "频道通知已更改!", | ||||||
|   "Notifications stream disabled!": "频道通知已禁用!", |   "Notifications stream disabled!": "频道通知已禁用!", | ||||||
|   "Old password": "旧密码", |   "Old password": "旧密码", | ||||||
|   "On __last_active__": "", |   "On __last_active__": "在__last_active__", | ||||||
|   "On __last_active_date__": "", |   "On __last_active_date__": "在__last_active_date__", | ||||||
|   "Only organization administrators can add bots to this organization": "只有组织管理员可以将机器人添加到该组织", |   "Only organization administrators can add bots to this organization": "只有组织管理员可以将机器人添加到该组织", | ||||||
|   "Only organization administrators can add custom emoji in this organization.": "只有社群管理员才能自定义表情在这个社群中。", |   "Only organization administrators can add custom emoji in this organization.": "只有社群管理员才能自定义表情在这个社群中。", | ||||||
|   "Only organization administrators can add generic bots": "只有组织管理员可以添加通用机器人", |   "Only organization administrators can add generic bots": "只有组织管理员可以添加通用机器人", | ||||||
|   "Only organization administrators can edit these settings.": "只有社群管理员才能编辑这些设置。", |   "Only organization administrators can edit these settings.": "只有社群管理员才能编辑这些设置。", | ||||||
|   "Only organization administrators can post.": "", |   "Only organization administrators can post.": "只有组织管理员才能发布。", | ||||||
|   "Only organization admins are allowed to post to this stream.": "", |   "Only organization admins are allowed to post to this stream.": "只有组织管理员可以发布到这个频道。", | ||||||
|   "Optional": "可选设置", |   "Optional": "可选设置", | ||||||
|   "Organization": "社群", |   "Organization": "社群", | ||||||
|   "Organization administrators can change this in the organization settings.": "", |   "Organization administrators can change this in the organization settings.": "组织管理员可以在组织设置中更改此设置。", | ||||||
|   "Organization avatar": "社群头像", |   "Organization avatar": "社群头像", | ||||||
|   "Organization description": "", |   "Organization description": "组织描述", | ||||||
|   "Organization name": "社群名称", |   "Organization name": "社群名称", | ||||||
|   "Organization permissions": "社群许可", |   "Organization permissions": "社群许可", | ||||||
|   "Organization profile": "社群资料", |   "Organization profile": "社群资料", | ||||||
|   "Organization settings": "社区设置", |   "Organization settings": "社区设置", | ||||||
|   "Other notification settings": "", |   "Other notification settings": "其他通知设置", | ||||||
|   "Other permissions": "", |   "Other permissions": "其他权限", | ||||||
|   "Outgoing webhook message format": "送出的webhook消息格式", |   "Outgoing webhook message format": "送出的webhook消息格式", | ||||||
|   "Owner": "所有者", |   "Owner": "所有者", | ||||||
|   "Password": "密码", |   "Password": "密码", | ||||||
| @@ -376,96 +376,96 @@ | |||||||
|   "Pin stream to top of left sidebar": "钉住频道在左侧栏的顶部", |   "Pin stream to top of left sidebar": "钉住频道在左侧栏的顶部", | ||||||
|   "Please just upload one file.": "请上传一个文件", |   "Please just upload one file.": "请上传一个文件", | ||||||
|   "Please re-enter your password to confirm your identity.": "请重新输入密码以确认你的身份。", |   "Please re-enter your password to confirm your identity.": "请重新输入密码以确认你的身份。", | ||||||
|   "Please specify a date or time": "", |   "Please specify a date or time": "请注明日期或时间", | ||||||
|   "Please specify a stream": "请指定频道", |   "Please specify a stream": "请指定频道", | ||||||
|   "Please specify a topic": "请指定话题", |   "Please specify a topic": "请指定话题", | ||||||
|   "Please specify at least one valid recipient": "", |   "Please specify at least one valid recipient": "请至少指定一个可用的收信人", | ||||||
|   "Prevent users from changing their email address": "阻止用户更改邮件地址", |   "Prevent users from changing their email address": "阻止用户更改邮件地址", | ||||||
|   "Prevent users from changing their name": "防止用户更改名称", |   "Prevent users from changing their name": "防止用户更改名称", | ||||||
|   "Preview profile": "", |   "Preview profile": "预览资料", | ||||||
|   "Private messages and @-mentions": "私信和@提醒", |   "Private messages and @-mentions": "私信和@提醒", | ||||||
|   "Profile": "", |   "Profile": "资料", | ||||||
|   "Profile field settings": "", |   "Profile field settings": "资料字段设置", | ||||||
|   "Question": "", |   "Question": "问题", | ||||||
|   "Quote and reply": "引用并回复", |   "Quote and reply": "引用并回复", | ||||||
|   "Reactivate": "启用", |   "Reactivate": "启用", | ||||||
|   "Reactivate bot": "重启机器人", |   "Reactivate bot": "重启机器人", | ||||||
|   "Regular expression": "正则表达式", |   "Regular expression": "正则表达式", | ||||||
|   "Remind me about this": "", |   "Remind me about this": "提醒我", | ||||||
|   "Reminder not set!": "", |   "Reminder not set!": "提醒没有设置!", | ||||||
|   "Reminder set!": "", |   "Reminder set!": "提醒已设置!", | ||||||
|   "Remove": "移除", |   "Remove": "移除", | ||||||
|   "Remove from default": "取消默认频道", |   "Remove from default": "取消默认频道", | ||||||
|   "Reply (r)": "", |   "Reply (r)": "回复(r)", | ||||||
|   "Reply mentioning user": "回复提到用户", |   "Reply mentioning user": "回复提到用户", | ||||||
|   "Require topics in stream messages": "频道消息中所需的主题", |   "Require topics in stream messages": "频道消息中所需的主题", | ||||||
|   "Resend": "", |   "Resend": "重新发送", | ||||||
|   "Resend invitation to <span class=\"email\"></span>": "", |   "Resend invitation to <span class=\"email\"></span>": "重新发送邀请到<span class=\"email\"></span>", | ||||||
|   "Resend now": "", |   "Resend now": "立即重新发送", | ||||||
|   "Resending encountered an error. Please reload and try again.": "", |   "Resending encountered an error. Please reload and try again.": "重发是发生错误。请刷新后再试", | ||||||
|   "Restore draft": "恢复草稿", |   "Restore draft": "恢复草稿", | ||||||
|   "Restrict email domains of new users?": "", |   "Restrict email domains of new users?": "限制新用户的电子邮件域?", | ||||||
|   "Restrict posting to organization administrators": "", |   "Restrict posting to organization administrators": "限制发布到组织管理员", | ||||||
|   "Restrict to a list of domains": "", |   "Restrict to a list of domains": "限制到一个域列表", | ||||||
|   "Retry": "重试", |   "Retry": "重试", | ||||||
|   "Revoke": "", |   "Revoke": "撤销", | ||||||
|   "Revoke invitation to <span class=\"email\"></span>": "", |   "Revoke invitation to <span class=\"email\"></span>": "撤销发送给<span class=\"email\"></span>的邀请", | ||||||
|   "Revoke now": "", |   "Revoke now": "立即撤销", | ||||||
|   "Role": "", |   "Role": "角色", | ||||||
|   "Save": "保存", |   "Save": "保存", | ||||||
|   "Save changes": "保存修改", |   "Save changes": "保存修改", | ||||||
|   "Save failed": "", |   "Save failed": "保存失败", | ||||||
|   "Saved": "", |   "Saved": "已保存", | ||||||
|   "Saved. Please <a class='reload_link'>reload</a> for the change to take effect.": "", |   "Saved. Please <a class='reload_link'>reload</a> for the change to take effect.": "已保存。请<a class='reload_link'>刷新</a>使更改生效", | ||||||
|   "Saving": "", |   "Saving": "保存中", | ||||||
|   "Search": "搜索", |   "Search": "搜索", | ||||||
|   "Search operators": "搜索管理者", |   "Search operators": "搜索管理者", | ||||||
|   "Search results": "搜索结果", |   "Search results": "搜索结果", | ||||||
|   "Search subscribers": "搜索订阅者", |   "Search subscribers": "搜索订阅者", | ||||||
|   "Search uploads...": "搜索已上传的文件", |   "Search uploads...": "搜索已上传的文件", | ||||||
|   "See the rest of this message": "查看其余内容", |   "See the rest of this message": "查看其余内容", | ||||||
|   "Select date and time": "", |   "Select date and time": "选择日期和时间", | ||||||
|   "Select default language": "选择默认语言", |   "Select default language": "选择默认语言", | ||||||
|   "Send digest emails when I'm away": "", |   "Send digest emails when I'm away": "当我离线时发送摘要邮件", | ||||||
|   "Send email notifications for new logins to my account": "", |   "Send email notifications for new logins to my account": "我的帐户进行新的登录时发送电子邮件通知", | ||||||
|   "Send emails introducing Zulip to new users": "", |   "Send emails introducing Zulip to new users": "向新用户发送介绍Zulip的电子邮件", | ||||||
|   "Send private message": "发送私有消息", |   "Send private message": "发送私有消息", | ||||||
|   "Sent!": "", |   "Sent!": "已发送!", | ||||||
|   "Settings": "设置", |   "Settings": "设置", | ||||||
|   "Setup": "", |   "Setup": "设置", | ||||||
|   "Setup two factor authentication": "", |   "Setup two factor authentication": "设置双重认证", | ||||||
|   "Show counts for starred messages": "", |   "Show counts for starred messages": "显示星标消息的数量", | ||||||
|   "Show previews of linked websites": "显示链接网站的预览", |   "Show previews of linked websites": "显示链接网站的预览", | ||||||
|   "Show previews of uploaded and linked images": "显示上传文件链接的图像预览", |   "Show previews of uploaded and linked images": "显示上传文件链接的图像预览", | ||||||
|   "Show/change your API key": "显示/修改您的 API Key", |   "Show/change your API key": "显示/修改您的 API Key", | ||||||
|   "Signup notifications stream changed!": "", |   "Signup notifications stream changed!": "频道注册通知已变更", | ||||||
|   "Signup notifications stream disabled!": "", |   "Signup notifications stream disabled!": "频道注册通知已禁用", | ||||||
|   "Size": "大小", |   "Size": "大小", | ||||||
|   "Slack compatible": "高度兼容", |   "Slack compatible": "高度兼容", | ||||||
|   "Slack's outgoing webhooks": "", |   "Slack's outgoing webhooks": "Slack发送的webhook", | ||||||
|   "Sorry, the file was too large.": "对不起,文件太大了。", |   "Sorry, the file was too large.": "对不起,文件太大了。", | ||||||
|   "Star": "星标", |   "Star": "星标", | ||||||
|   "Stream": "频道", |   "Stream": "频道", | ||||||
|   "Stream color": "频道颜色", |   "Stream color": "频道颜色", | ||||||
|   "Stream created recently": "", |   "Stream created recently": "最近创建的频道", | ||||||
|   "Stream creation": "频道创建", |   "Stream creation": "频道创建", | ||||||
|   "Stream description": "频道描述", |   "Stream description": "频道描述", | ||||||
|   "Stream description (optional)": "频道描述(可选)", |   "Stream description (optional)": "频道描述(可选)", | ||||||
|   "Stream membership": "频道用户", |   "Stream membership": "频道用户", | ||||||
|   "Stream messages": "频道消息", |   "Stream messages": "频道消息", | ||||||
|   "Stream name": "频道名称", |   "Stream name": "频道名称", | ||||||
|   "Stream permissions": "", |   "Stream permissions": "频道权限", | ||||||
|   "Stream settings": "频道设置", |   "Stream settings": "频道设置", | ||||||
|   "Stream successfully created!": "", |   "Stream successfully created!": "频道创建成功", | ||||||
|   "Streams": "频道", |   "Streams": "频道", | ||||||
|   "Submit": "", |   "Submit": "提交", | ||||||
|   "Subscribe": "订阅", |   "Subscribe": "订阅", | ||||||
|   "Subscribed": "已订阅", |   "Subscribed": "已订阅", | ||||||
|   "Subscribed successfully!": "", |   "Subscribed successfully!": "订阅成功", | ||||||
|   "Subscriber count": "", |   "Subscriber count": "订阅者数量", | ||||||
|   "Subscribers": "订阅者", |   "Subscribers": "订阅者", | ||||||
|   "Task already exists": "", |   "Task already exists": "任务已经存在", | ||||||
|   "Text": "", |   "Text": "文本", | ||||||
|   "The email body will become the Zulip message": "电子邮件正文将成为Zulip消息", |   "The email body will become the Zulip message": "电子邮件正文将成为Zulip消息", | ||||||
|   "The email subject will become the Zulip topic": "电子邮件正文将成为Zulip话题", |   "The email subject will become the Zulip topic": "电子邮件正文将成为Zulip话题", | ||||||
|   "The email will be forwarded to this stream": "邮件将会转发到这个频道中", |   "The email will be forwarded to this stream": "邮件将会转发到这个频道中", | ||||||
| @@ -474,27 +474,27 @@ | |||||||
|   "The stream description has been updated!": "频道描述信息已更新", |   "The stream description has been updated!": "频道描述信息已更新", | ||||||
|   "The stream has been renamed!": "频道重命名成功!", |   "The stream has been renamed!": "频道重命名成功!", | ||||||
|   "The stream to which new stream notifications go to.": "新流通知发送到的频道。", |   "The stream to which new stream notifications go to.": "新流通知发送到的频道。", | ||||||
|   "The stream which new user signup notifications go to.": "", |   "The stream which new user signup notifications go to.": "新用户注册通知频道", | ||||||
|   "Their password will be cleared from our systems, and any bots they maintain will be disabled.": "这些用户的密码会被从系统中清除,他们的机器人用户也会被关闭。", |   "Their password will be cleared from our systems, and any bots they maintain will be disabled.": "这些用户的密码会被从系统中清除,他们的机器人用户也会被关闭。", | ||||||
|   "There are no messages to reply to.": "", |   "There are no messages to reply to.": "没有消息可回复", | ||||||
|   "These settings are explained in detail in the <a target=\"_blank\" href=\"/help/stream-permissions\">help center</a>.": "", |   "These settings are explained in detail in the <a target=\"_blank\" href=\"/help/stream-permissions\">help center</a>.": "这些设置在<a target=\"_blank\" href=\"/help/stream-permissions\">帮助中心</a>中有详细说明。", | ||||||
|   "This action is permanent and cannot be undone. All users will permanently lose access to their Zulip accounts.": "", |   "This action is permanent and cannot be undone. All users will permanently lose access to their Zulip accounts.": "这项操作是永久且不可撤销的。所有用户将永久失去对Zulip账户的访问权限。", | ||||||
|   "This is a <span class=\"fa fa-globe\" aria-hidden=\"true\"></span> <b>public stream</b>. Anybody in your organization can join.": "", |   "This is a <span class=\"fa fa-globe\" aria-hidden=\"true\"></span> <b>public stream</b>. Anybody in your organization can join.": "这是一个<span class=\"fa fa-globe\" aria-hidden=\"true\"></span><b>公共频道</b>。所有组织成员都可以加入", | ||||||
|   "This is a <span class=\"fa fa-lock\" aria-hidden=\"true\"></span> <b>private stream</b>. Only people who have been invited can access its content, but any member of the stream can invite others.": "", |   "This is a <span class=\"fa fa-lock\" aria-hidden=\"true\"></span> <b>private stream</b>. Only people who have been invited can access its content, but any member of the stream can invite others.": "这是一个<span class=\"fa fa-lock\" aria-hidden=\"true\"></span><b>私有频道</b>。仅有邀请的用户可以对该频道进行访问,该频道的用户也可以邀请其它用户。", | ||||||
|   "This is a private stream": "这是一个私有频道", |   "This is a private stream": "这是一个私有频道", | ||||||
|   "This organization is configured to restrict editing of message content to __minutes_to_edit__ minutes after it is sent.": "这个社群组织已限制讯息发送间隔,请与 __minutes_to_edit__ 分钟后再发。", |   "This organization is configured to restrict editing of message content to __minutes_to_edit__ minutes after it is sent.": "这个社群组织已限制讯息发送间隔,请与 __minutes_to_edit__ 分钟后再发。", | ||||||
|   "This stream is reserved for <strong>announcements</strong>. <br /> Are you sure you want to message all <strong>__count__</strong> people in this stream?": "", |   "This stream is reserved for <strong>announcements</strong>. <br /> Are you sure you want to message all <strong>__count__</strong> people in this stream?": "此频道用于<strong>公告</strong>。<br />您确定向频道中所有<strong>__count__</strong>人发送消息吗?", | ||||||
|   "Time settings": "时间设置", |   "Time settings": "时间设置", | ||||||
|   "Time zone": "时区", |   "Time zone": "时区", | ||||||
|   "Time's up!": "时间到了!", |   "Time's up!": "时间到了!", | ||||||
|   "Today": "今日", |   "Today": "今日", | ||||||
|   "Toggle subscription": "触发订阅", |   "Toggle subscription": "触发订阅", | ||||||
|   "Tomorrow": "", |   "Tomorrow": "明天", | ||||||
|   "Topic": "话题", |   "Topic": "话题", | ||||||
|   "Topic editing only": "只能主题编辑", |   "Topic editing only": "只能主题编辑", | ||||||
|   "Try again": "再试一次", |   "Try again": "再试一次", | ||||||
|   "Two factor authentication": "", |   "Two factor authentication": "双重认证", | ||||||
|   "Type": "", |   "Type": "类型", | ||||||
|   "URL format string": "URL格式", |   "URL format string": "URL格式", | ||||||
|   "Un-collapse": "展开", |   "Un-collapse": "展开", | ||||||
|   "Unable to upload that many files at once.": "无法一次上传这么多的文件。", |   "Unable to upload that many files at once.": "无法一次上传这么多的文件。", | ||||||
| @@ -507,35 +507,35 @@ | |||||||
|   "Unpin stream <b>__stream.name__</b> from top": "取消频道\"<b>__stream.name__</b>\"置顶", |   "Unpin stream <b>__stream.name__</b> from top": "取消频道\"<b>__stream.name__</b>\"置顶", | ||||||
|   "Unstar": "取消星标", |   "Unstar": "取消星标", | ||||||
|   "Unsubscribe": "退订", |   "Unsubscribe": "退订", | ||||||
|   "Unsubscribed successfully!": "", |   "Unsubscribed successfully!": "退订成功", | ||||||
|   "Up to N minutes after posting": "", |   "Up to N minutes after posting": "发布后N分钟", | ||||||
|   "Up to __time_limit__ after posting": "", |   "Up to __time_limit__ after posting": "发布后__time_limit__", | ||||||
|   "Update successful: Subdomains allowed for __domain__": "更新成功:允许新的域名于__domain__", |   "Update successful: Subdomains allowed for __domain__": "更新成功:允许新的域名于__domain__", | ||||||
|   "Update successful: Subdomains no longer allowed for __domain__": "更新成功:不在允许这个域名 __domain__", |   "Update successful: Subdomains no longer allowed for __domain__": "更新成功:不在允许这个域名 __domain__", | ||||||
|   "Updated settings!": "", |   "Updated settings!": "更新设置", | ||||||
|   "Updated successfully!": "更新成功!", |   "Updated successfully!": "更新成功!", | ||||||
|   "Upload avatar": "上传头像", |   "Upload avatar": "上传头像", | ||||||
|   "Upload icon": "上传图标", |   "Upload icon": "上传图标", | ||||||
|   "Upload image or GIF": "", |   "Upload image or GIF": "上传图片", | ||||||
|   "Upload new avatar": "上传一个新头像", |   "Upload new avatar": "上传一个新头像", | ||||||
|   "Upload new icon": "上传新图标", |   "Upload new icon": "上传新图标", | ||||||
|   "Uploaded files": "已上传文件", |   "Uploaded files": "已上传文件", | ||||||
|   "Uploading icon.": "图标上传中", |   "Uploading icon.": "图标上传中", | ||||||
|   "Uploading\u2026": "上传", |   "Uploading\u2026": "上传", | ||||||
|   "User already subscribed.": "", |   "User already subscribed.": "用户已经订阅", | ||||||
|   "User avatar": "用户头像", |   "User avatar": "用户头像", | ||||||
|   "User group added!": "", |   "User group added!": "用户组已添加", | ||||||
|   "User groups": "用户组", |   "User groups": "用户组", | ||||||
|   "User identity": "用户标识", |   "User identity": "用户标识", | ||||||
|   "User is already not subscribed.": "", |   "User is already not subscribed.": "用户没有订阅", | ||||||
|   "User list on left sidebar in narrow windows": "窗口右侧变懒的用户列表", |   "User list on left sidebar in narrow windows": "窗口右侧变懒的用户列表", | ||||||
|   "User role": "", |   "User role": "用户角色", | ||||||
|   "User settings": "用户设置", |   "User settings": "用户设置", | ||||||
|   "User(s) invited successfully.": "", |   "User(s) invited successfully.": "用户邀请成功", | ||||||
|   "Username": "用户名", |   "Username": "用户名", | ||||||
|   "Username (a-z, 0-9, and dashes only)": "", |   "Username (a-z, 0-9, and dashes only)": "用户名(字母、数字和下划线)", | ||||||
|   "Users can edit the topic of any message": "", |   "Users can edit the topic of any message": "用户可以编辑消息的主题", | ||||||
|   "Video chat provider": "", |   "Video chat provider": "视频聊天提供者", | ||||||
|   "View edit history": "显示编辑历史 ", |   "View edit history": "显示编辑历史 ", | ||||||
|   "View file": "显示文件", |   "View file": "显示文件", | ||||||
|   "View messages sent": "显示已发送消息", |   "View messages sent": "显示已发送消息", | ||||||
| @@ -543,58 +543,58 @@ | |||||||
|   "View source": "显示源", |   "View source": "显示源", | ||||||
|   "View source / Edit topic": "查看源 / 编辑主题", |   "View source / Edit topic": "查看源 / 编辑主题", | ||||||
|   "View stream": "显示频道", |   "View stream": "显示频道", | ||||||
|   "View user profile": "", |   "View user profile": "查看用户资料", | ||||||
|   "View your profile": "", |   "View your profile": "查看我的资料", | ||||||
|   "Visual desktop notifications": "", |   "Visual desktop notifications": "可视桌面通知", | ||||||
|   "Warning: <strong>__stream_name__</strong> is a private stream.": "", |   "Warning: <strong>__stream_name__</strong> is a private stream.": "警告:<strong>__stream_name__</strong>是私有频道", | ||||||
|   "Who can add bots": "", |   "Who can add bots": "谁能添加机器人", | ||||||
|   "Who can add custom emoji": "", |   "Who can add custom emoji": "谁能添加自定义表情", | ||||||
|   "Who can create streams": "", |   "Who can create streams": "谁能创建频道", | ||||||
|   "Working\u2026": "进行中", |   "Working\u2026": "进行中", | ||||||
|   "Yes": "是", |   "Yes": "是", | ||||||
|   "Yes, delete this stream": "是的,删除该频道", |   "Yes, delete this stream": "是的,删除该频道", | ||||||
|   "Yes, send": "是的,发送", |   "Yes, send": "是的,发送", | ||||||
|   "Yes, subscribe __count__ users!": "确定,订阅 __count__  用户!", |   "Yes, subscribe __count__ users!": "确定,订阅 __count__  用户!", | ||||||
|   "Yes. Members and admins can send invitations.": "", |   "Yes. Members and admins can send invitations.": "是的,普通成员和管理员可以发送邀请", | ||||||
|   "Yes. Only admins can send invitations.": "", |   "Yes. Only admins can send invitations.": "是的,只有管理员可以发送邀请", | ||||||
|   "Yesterday": "昨天", |   "Yesterday": "昨天", | ||||||
|   "You and __display_reply_to__": "您和__display_reply_to__", |   "You and __display_reply_to__": "您和__display_reply_to__", | ||||||
|   "You and __recipients__": "你和 __recipients__", |   "You and __recipients__": "你和 __recipients__", | ||||||
|   "You are not currently subscribed to this stream.": "您目前未订阅此频道。", |   "You are not currently subscribed to this stream.": "您目前未订阅此频道。", | ||||||
|   "You are not subscribed to stream __stream__": "你没有订阅__stream__频道", |   "You are not subscribed to stream __stream__": "你没有订阅__stream__频道", | ||||||
|   "You can send emails to Zulip! Just copy and use this address as an email recipient, and:": "您可以发送电子邮件给Zulip! 只需复制并使用此地址作为电子邮件收件人,并且:", |   "You can send emails to Zulip! Just copy and use this address as an email recipient, and:": "您可以发送电子邮件给Zulip! 只需复制并使用此地址作为电子邮件收件人,并且:", | ||||||
|   "You cannot create a stream with no subscribers!": "", |   "You cannot create a stream with no subscribers!": "创建频道时必须有订阅者", | ||||||
|   "You have no active bots.": "你没有可用的机器人。", |   "You have no active bots.": "你没有可用的机器人。", | ||||||
|   "You have no inactive bots.": "你没有不可用的机器人。", |   "You have no inactive bots.": "你没有不可用的机器人。", | ||||||
|   "You have not muted any topics yet.": "你还没有任何静音的话题", |   "You have not muted any topics yet.": "你还没有任何静音的话题", | ||||||
|   "You have not uploaded any files.": "目前没有上传任何文件。", |   "You have not uploaded any files.": "目前没有上传任何文件。", | ||||||
|   "You have nothing to send!": "消息不能为空!", |   "You have nothing to send!": "消息不能为空!", | ||||||
|   "You must be an organization administrator to create a stream without subscribing.": "", |   "You must be an organization administrator to create a stream without subscribing.": "您必须是组织管理员才能在不订阅的情况下创建频道", | ||||||
|   "You need to be running Zephyr mirroring in order to send messages!": "您需要运行Zephyr镜像服务以便发送消息!", |   "You need to be running Zephyr mirroring in order to send messages!": "您需要运行Zephyr镜像服务以便发送消息!", | ||||||
|   "You subscribed to stream __stream__": "你订阅了 __stream__ 频道", |   "You subscribed to stream __stream__": "你订阅了 __stream__ 频道", | ||||||
|   "You unsubscribed from stream __stream__": "你取消订阅了 __stream__ 频道", |   "You unsubscribed from stream __stream__": "你取消订阅了 __stream__ 频道", | ||||||
|   "You're not subscribed to this stream. You will not be notified if other users reply to your message.": "", |   "You're not subscribed to this stream. You will not be notified if other users reply to your message.": "您没有订阅这个频道。如果其他用户回复您的邮件,您将不会收到通知。", | ||||||
|   "Your API key:": "您的 API Key:", |   "Your API key:": "您的 API Key:", | ||||||
|   "Your account": "你的账户", |   "Your account": "你的账户", | ||||||
|   "Your bots": "你的机器人", |   "Your bots": "你的机器人", | ||||||
|   "Your reminder note is empty!": "", |   "Your reminder note is empty!": "您没有提醒事项", | ||||||
|   "[Condense this message]": "[收起消息]", |   "[Condense this message]": "[收起消息]", | ||||||
|   "[Configure]": "", |   "[Configure]": "[配置]", | ||||||
|   "[Disable]": "[禁用]", |   "[Disable]": "[禁用]", | ||||||
|   "[More...]": "[更多...]", |   "[More...]": "[更多...]", | ||||||
|   "__hours__ hours ago": "", |   "__hours__ hours ago": "__hours__小时以前", | ||||||
|   "__minutes__ min to edit": "__minutes__分钟内完成编辑", |   "__minutes__ min to edit": "__minutes__分钟内完成编辑", | ||||||
|   "__minutes__ minutes ago": "", |   "__minutes__ minutes ago": "__minutes__分钟以前", | ||||||
|   "__seconds__ sec to edit": "__seconds__秒内完成编辑", |   "__seconds__ sec to edit": "__seconds__秒内完成编辑", | ||||||
|   "__starred_status__ this message": "__starred_status__这个消息", |   "__starred_status__ this message": "__starred_status__这个消息", | ||||||
|   "__wildcard_mention_token__ (Notify stream)": "", |   "__wildcard_mention_token__ (Notify stream)": "__wildcard_mention_token__ (通知频道)", | ||||||
|   "and": "来", |   "and": "来", | ||||||
|   "cookie": "cookie", |   "cookie": "cookie", | ||||||
|   "in 1 hour": "1小时内", |   "in 1 hour": "1小时内", | ||||||
|   "in 20 minutes": "20分钟内", |   "in 20 minutes": "20分钟内", | ||||||
|   "in 3 hours": "3小时内", |   "in 3 hours": "3小时内", | ||||||
|   "leafy green vegetable": "", |   "leafy green vegetable": "绿叶蔬菜", | ||||||
|   "marketing": "", |   "marketing": "销售", | ||||||
|   "more conversations": "更多会话", |   "more conversations": "更多会话", | ||||||
|   "more topics": "更多话题" |   "more topics": "更多话题" | ||||||
| } | } | ||||||
| @@ -33,9 +33,10 @@ organization first. | |||||||
|  |  | ||||||
| ### Import into a self-hosted Zulip server | ### Import into a self-hosted Zulip server | ||||||
|  |  | ||||||
| Because the import tool is very new, you will need to | First | ||||||
| upgrade your Zulip server to the latest `master` branch, | [install a new Zulip server](https://zulip.readthedocs.io/en/stable/production/install.html), | ||||||
| using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | skipping "Step 3: Create a Zulip organization, and log in" (you'll | ||||||
|  | create your Zulip organization via the data import tool instead). | ||||||
|  |  | ||||||
| Log in to a shell on your Zulip server as the `zulip` user. To import with | Log in to a shell on your Zulip server as the `zulip` user. To import with | ||||||
| the most common configuration, run the following commands, replacing | the most common configuration, run the following commands, replacing | ||||||
|   | |||||||
| @@ -65,6 +65,11 @@ organization first. | |||||||
|  |  | ||||||
| ### Import into a self-hosted Zulip server | ### Import into a self-hosted Zulip server | ||||||
|  |  | ||||||
|  | First | ||||||
|  | [install a new Zulip server](https://zulip.readthedocs.io/en/stable/production/install.html), | ||||||
|  | skipping "Step 3: Create a Zulip organization, and log in" (you'll | ||||||
|  | create your Zulip organization via the data import tool instead). | ||||||
|  |  | ||||||
| Because the import tool is very new, you will need to | Because the import tool is very new, you will need to | ||||||
| upgrade your Zulip server to the latest `master` branch, | upgrade your Zulip server to the latest `master` branch, | ||||||
| using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | ||||||
|   | |||||||
| @@ -35,9 +35,10 @@ organization first. | |||||||
|  |  | ||||||
| ### Import into a self-hosted Zulip server | ### Import into a self-hosted Zulip server | ||||||
|  |  | ||||||
| Because the import tool is very new, you will need to | First | ||||||
| upgrade your Zulip server to the latest `master` branch, | [install a new Zulip server](https://zulip.readthedocs.io/en/stable/production/install.html), | ||||||
| using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | skipping "Step 3: Create a Zulip organization, and log in" (you'll | ||||||
|  | create your Zulip organization via the data import tool instead). | ||||||
|  |  | ||||||
| Log in to a shell on your Zulip server as the `zulip` user. To import with | Log in to a shell on your Zulip server as the `zulip` user. To import with | ||||||
| the most common configuration, run the following commands, replacing | the most common configuration, run the following commands, replacing | ||||||
|   | |||||||
| @@ -770,6 +770,10 @@ def build_custom_checkers(by_lang): | |||||||
|          'include_only': set(['docs/']), |          'include_only': set(['docs/']), | ||||||
|          'description': "Use relative links (../foo/bar.html) to other documents in docs/", |          'description': "Use relative links (../foo/bar.html) to other documents in docs/", | ||||||
|          }, |          }, | ||||||
|  |         {'pattern': "su zulip -c [^']", | ||||||
|  |          'include_only': set(['docs/']), | ||||||
|  |          'description': "Always quote arguments using `su zulip -c '` to avoid confusion about how su works.", | ||||||
|  |          }, | ||||||
|         {'pattern': r'\][(][^#h]', |         {'pattern': r'\][(][^#h]', | ||||||
|          'include_only': set(['README.md', 'CONTRIBUTING.md']), |          'include_only': set(['README.md', 'CONTRIBUTING.md']), | ||||||
|          'description': "Use absolute links from docs served by GitHub", |          'description': "Use absolute links from docs served by GitHub", | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| ZULIP_VERSION = "1.9.0" | ZULIP_VERSION = "1.9.1" | ||||||
|  |  | ||||||
| # Bump the minor PROVISION_VERSION to indicate that folks should provision | # Bump the minor PROVISION_VERSION to indicate that folks should provision | ||||||
| # only when going from an old version of the code to a newer version. Bump | # only when going from an old version of the code to a newer version. Bump | ||||||
|   | |||||||
| @@ -388,11 +388,11 @@ def process_avatars(avatar_list: List[ZerverFieldsT], avatar_dir: str, realm_id: | |||||||
|     downloaded.  For simpler conversions see write_avatar_png. |     downloaded.  For simpler conversions see write_avatar_png. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def get_avatar(avatar_upload_list: List[str]) -> int: |     def get_avatar(avatar_upload_item: List[str]) -> int: | ||||||
|         avatar_url = avatar_upload_list[0] |         avatar_url = avatar_upload_item[0] | ||||||
|  |  | ||||||
|         image_path = os.path.join(avatar_dir, avatar_original_list[1]) |         image_path = os.path.join(avatar_dir, avatar_upload_item[1]) | ||||||
|         original_image_path = os.path.join(avatar_dir, avatar_original_list[2]) |         original_image_path = os.path.join(avatar_dir, avatar_upload_item[2]) | ||||||
|  |  | ||||||
|         response = requests.get(avatar_url + size_url_suffix, stream=True) |         response = requests.get(avatar_url + size_url_suffix, stream=True) | ||||||
|         with open(image_path, 'wb') as image_file: |         with open(image_path, 'wb') as image_file: | ||||||
|   | |||||||
| @@ -599,7 +599,8 @@ def import_uploads_s3(bucket_name: str, import_dir: Path, processing_avatars: bo | |||||||
|             user_profile = get_user_profile_by_id(user_profile_id) |             user_profile = get_user_profile_by_id(user_profile_id) | ||||||
|             key.set_metadata("user_profile_id", str(user_profile.id)) |             key.set_metadata("user_profile_id", str(user_profile.id)) | ||||||
|  |  | ||||||
|         key.set_metadata("orig_last_modified", record['last_modified']) |         if 'last_modified' in record: | ||||||
|  |             key.set_metadata("orig_last_modified", record['last_modified']) | ||||||
|         key.set_metadata("realm_id", str(record['realm_id'])) |         key.set_metadata("realm_id", str(record['realm_id'])) | ||||||
|  |  | ||||||
|         # Zulip exports will always have a content-type, but third-party exports might not. |         # Zulip exports will always have a content-type, but third-party exports might not. | ||||||
|   | |||||||
| @@ -114,8 +114,8 @@ def send_apple_push_notification(user_id: int, devices: List[DeviceToken], | |||||||
|  |  | ||||||
|     client = get_apns_client()  # type: APNsClient |     client = get_apns_client()  # type: APNsClient | ||||||
|     if client is None: |     if client is None: | ||||||
|         logging.warning("APNs: Dropping a notification because nothing configured.  " |         logging.debug("APNs: Dropping a notification because nothing configured.  " | ||||||
|                         "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).") |                       "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).") | ||||||
|         return |         return | ||||||
|  |  | ||||||
|     if remote: |     if remote: | ||||||
| @@ -186,8 +186,8 @@ def send_android_push_notification_to_user(user_profile: UserProfile, data: Dict | |||||||
| def send_android_push_notification(devices: List[DeviceToken], data: Dict[str, Any], | def send_android_push_notification(devices: List[DeviceToken], data: Dict[str, Any], | ||||||
|                                    remote: bool=False) -> None: |                                    remote: bool=False) -> None: | ||||||
|     if not gcm: |     if not gcm: | ||||||
|         logging.warning("Skipping sending a GCM push notification since " |         logging.debug("Skipping sending a GCM push notification since " | ||||||
|                         "PUSH_NOTIFICATION_BOUNCER_URL and ANDROID_GCM_API_KEY are both unset") |                       "PUSH_NOTIFICATION_BOUNCER_URL and ANDROID_GCM_API_KEY are both unset") | ||||||
|         return |         return | ||||||
|     reg_ids = [device.token for device in devices] |     reg_ids = [device.token for device in devices] | ||||||
|  |  | ||||||
| @@ -429,6 +429,12 @@ def push_notifications_enabled() -> bool: | |||||||
|         return True |         return True | ||||||
|     return False |     return False | ||||||
|  |  | ||||||
|  | def initialize_push_notifications() -> None: | ||||||
|  |     if not push_notifications_enabled(): | ||||||
|  |         logging.warning("Mobile push notifications are not configured.\n  " | ||||||
|  |                         "See https://zulip.readthedocs.io/en/latest/" | ||||||
|  |                         "production/mobile-push-notifications.html") | ||||||
|  |  | ||||||
| def get_gcm_alert(message: Message) -> str: | def get_gcm_alert(message: Message) -> str: | ||||||
|     """ |     """ | ||||||
|     Determine what alert string to display based on the missed messages. |     Determine what alert string to display based on the missed messages. | ||||||
| @@ -637,7 +643,7 @@ def handle_push_notification(user_profile_id: int, missed_message: Dict[str, Any | |||||||
|     user_profile = get_user_profile_by_id(user_profile_id) |     user_profile = get_user_profile_by_id(user_profile_id) | ||||||
|     (message, user_message) = access_message(user_profile, missed_message['message_id']) |     (message, user_message) = access_message(user_profile, missed_message['message_id']) | ||||||
|     if user_message is not None: |     if user_message is not None: | ||||||
|         # If ther user has read the message already, don't push-notify. |         # If the user has read the message already, don't push-notify. | ||||||
|         # |         # | ||||||
|         # TODO: It feels like this is already handled when things are |         # TODO: It feels like this is already handled when things are | ||||||
|         # put in the queue; maybe we should centralize this logic with |         # put in the queue; maybe we should centralize this logic with | ||||||
|   | |||||||
| @@ -431,7 +431,7 @@ class S3UploadBackend(ZulipUploadBackend): | |||||||
|         bucket_name = settings.S3_AVATAR_BUCKET |         bucket_name = settings.S3_AVATAR_BUCKET | ||||||
|         conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY) |         conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY) | ||||||
|         bucket = get_bucket(conn, bucket_name) |         bucket = get_bucket(conn, bucket_name) | ||||||
|         key = bucket.get_key(file_path) |         key = bucket.get_key(file_path + ".original") | ||||||
|         image_data = key.get_contents_as_string() |         image_data = key.get_contents_as_string() | ||||||
|  |  | ||||||
|         resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)  # type: ignore # image_data is `bytes`, boto subs are wrong |         resized_medium = resize_avatar(image_data, MEDIUM_AVATAR_SIZE)  # type: ignore # image_data is `bytes`, boto subs are wrong | ||||||
|   | |||||||
| @@ -3,26 +3,20 @@ import sys | |||||||
| from argparse import ArgumentParser | from argparse import ArgumentParser | ||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| from django.core.management.base import BaseCommand | from django.core.management.base import CommandError | ||||||
|  |  | ||||||
| from zerver.models import Realm, get_realm | from zerver.lib.management import ZulipBaseCommand | ||||||
|  | from zerver.models import get_realm | ||||||
|  |  | ||||||
| class Command(BaseCommand): | class Command(ZulipBaseCommand): | ||||||
|     help = """Show the admins in a realm.""" |     help = """Show the admins in a realm.""" | ||||||
|  |  | ||||||
|     def add_arguments(self, parser: ArgumentParser) -> None: |     def add_arguments(self, parser: ArgumentParser) -> None: | ||||||
|         parser.add_argument('realm', metavar='<realm>', type=str, |         self.add_realm_args(parser, required=True) | ||||||
|                             help="realm to show admins for") |  | ||||||
|  |  | ||||||
|     def handle(self, *args: Any, **options: str) -> None: |  | ||||||
|         realm_name = options['realm'] |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             realm = get_realm(realm_name) |  | ||||||
|         except Realm.DoesNotExist: |  | ||||||
|             print('There is no realm called %s.' % (realm_name,)) |  | ||||||
|             sys.exit(1) |  | ||||||
|  |  | ||||||
|  |     def handle(self, *args: Any, **options: Any) -> None: | ||||||
|  |         realm = self.get_realm(options) | ||||||
|  |         assert realm is not None  # True because of required=True above | ||||||
|         users = realm.get_admin_users() |         users = realm.get_admin_users() | ||||||
|  |  | ||||||
|         if users: |         if users: | ||||||
| @@ -32,4 +26,5 @@ class Command(BaseCommand): | |||||||
|         else: |         else: | ||||||
|             print('There are no admins for this realm!') |             print('There are no admins for this realm!') | ||||||
|  |  | ||||||
|         print('\nYou can use the "knight" management command to knight admins.') |         print('\nYou can use the "knight" management command to make more users admins.') | ||||||
|  |         print('\nOr with the --revoke argument, remove admin status from users.') | ||||||
|   | |||||||
| @@ -668,10 +668,15 @@ class TestAPNs(PushNotificationTest): | |||||||
|                 mock.patch('zerver.lib.push_notifications.logging') as mock_logging: |                 mock.patch('zerver.lib.push_notifications.logging') as mock_logging: | ||||||
|             mock_get.return_value = None |             mock_get.return_value = None | ||||||
|             self.send() |             self.send() | ||||||
|             mock_logging.warning.assert_called_once_with( |             mock_logging.debug.assert_called_once_with( | ||||||
|                 "APNs: Dropping a notification because nothing configured.  " |                 "APNs: Dropping a notification because nothing configured.  " | ||||||
|                 "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).") |                 "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).") | ||||||
|             mock_logging.info.assert_not_called() |             mock_logging.warning.assert_not_called() | ||||||
|  |             from zerver.lib.push_notifications import initialize_push_notifications | ||||||
|  |             initialize_push_notifications() | ||||||
|  |             mock_logging.warning.assert_called_once_with( | ||||||
|  |                 "Mobile push notifications are not configured.\n  " | ||||||
|  |                 "See https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html") | ||||||
|  |  | ||||||
|     def test_success(self) -> None: |     def test_success(self) -> None: | ||||||
|         with self.mock_apns() as mock_apns, \ |         with self.mock_apns() as mock_apns, \ | ||||||
| @@ -1154,11 +1159,11 @@ class GCMTest(PushNotificationTest): | |||||||
|         return data |         return data | ||||||
|  |  | ||||||
| class GCMNotSetTest(GCMTest): | class GCMNotSetTest(GCMTest): | ||||||
|     @mock.patch('logging.warning') |     @mock.patch('logging.debug') | ||||||
|     def test_gcm_is_none(self, mock_warning: mock.MagicMock) -> None: |     def test_gcm_is_none(self, mock_debug: mock.MagicMock) -> None: | ||||||
|         apn.gcm = None |         apn.gcm = None | ||||||
|         apn.send_android_push_notification_to_user(self.user_profile, {}) |         apn.send_android_push_notification_to_user(self.user_profile, {}) | ||||||
|         mock_warning.assert_called_with( |         mock_debug.assert_called_with( | ||||||
|             "Skipping sending a GCM push notification since PUSH_NOTIFICATION_BOUNCER_URL " |             "Skipping sending a GCM push notification since PUSH_NOTIFICATION_BOUNCER_URL " | ||||||
|             "and ANDROID_GCM_API_KEY are both unset") |             "and ANDROID_GCM_API_KEY are both unset") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -348,6 +348,7 @@ def send_oauth_request_to_google(request: HttpRequest) -> HttpResponse: | |||||||
|         'redirect_uri': reverse_on_root('zerver.views.auth.finish_google_oauth2'), |         'redirect_uri': reverse_on_root('zerver.views.auth.finish_google_oauth2'), | ||||||
|         'scope': 'profile email', |         'scope': 'profile email', | ||||||
|         'state': csrf_state, |         'state': csrf_state, | ||||||
|  |         'prompt': 'select_account', | ||||||
|     } |     } | ||||||
|     return redirect(google_uri + urllib.parse.urlencode(params)) |     return redirect(google_uri + urllib.parse.urlencode(params)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,8 @@ from zerver.lib.feedback import handle_feedback | |||||||
| from zerver.lib.queue import SimpleQueueClient, queue_json_publish, retry_event | from zerver.lib.queue import SimpleQueueClient, queue_json_publish, retry_event | ||||||
| from zerver.lib.timestamp import timestamp_to_datetime | from zerver.lib.timestamp import timestamp_to_datetime | ||||||
| from zerver.lib.notifications import handle_missedmessage_emails | from zerver.lib.notifications import handle_missedmessage_emails | ||||||
| from zerver.lib.push_notifications import handle_push_notification, handle_remove_push_notification | from zerver.lib.push_notifications import handle_push_notification, handle_remove_push_notification, \ | ||||||
|  |     initialize_push_notifications | ||||||
| from zerver.lib.actions import do_send_confirmation_email, \ | from zerver.lib.actions import do_send_confirmation_email, \ | ||||||
|     do_update_user_activity, do_update_user_activity_interval, do_update_user_presence, \ |     do_update_user_activity, do_update_user_activity_interval, do_update_user_presence, \ | ||||||
|     internal_send_message, check_send_message, extract_recipients, \ |     internal_send_message, check_send_message, extract_recipients, \ | ||||||
| @@ -352,6 +353,13 @@ class MissedMessageSendingWorker(EmailSendingWorker):  # nocoverage | |||||||
|  |  | ||||||
| @assign_queue('missedmessage_mobile_notifications') | @assign_queue('missedmessage_mobile_notifications') | ||||||
| class PushNotificationsWorker(QueueProcessingWorker):  # nocoverage | class PushNotificationsWorker(QueueProcessingWorker):  # nocoverage | ||||||
|  |     def start(self) -> None: | ||||||
|  |         # initialize_push_notifications doesn't strictly do anything | ||||||
|  |         # beyond printing some logging warnings if push notifications | ||||||
|  |         # are not available in the current configuration. | ||||||
|  |         initialize_push_notifications() | ||||||
|  |         super().start() | ||||||
|  |  | ||||||
|     def consume(self, data: Mapping[str, Any]) -> None: |     def consume(self, data: Mapping[str, Any]) -> None: | ||||||
|         if data.get("type", "add") == "remove": |         if data.get("type", "add") == "remove": | ||||||
|             handle_remove_push_notification(data['user_profile_id'], data['message_id']) |             handle_remove_push_notification(data['user_profile_id'], data['message_id']) | ||||||
|   | |||||||
| @@ -71,8 +71,13 @@ ZULIP_ADMINISTRATOR = 'zulip-admin@example.com' | |||||||
| # The noreply address to be used as the sender for certain generated | # The noreply address to be used as the sender for certain generated | ||||||
| # emails.  Messages sent to this address could contain sensitive user | # emails.  Messages sent to this address could contain sensitive user | ||||||
| # data and should not be delivered anywhere.  The default is | # data and should not be delivered anywhere.  The default is | ||||||
| # e.g. noreply@zulip.example.com (if EXTERNAL_HOST is | # e.g. noreply-{random_token}@zulip.example.com (if EXTERNAL_HOST is | ||||||
| # zulip.example.com). | # zulip.example.com).  There are potential security issues if you set | ||||||
|  | # ADD_TOKENS_TO_NOREPLY_ADDRESS=False to remove the token; see | ||||||
|  | # https://zulip.readthedocs.io/en/latest/production/email.html for details. | ||||||
|  | #ADD_TOKENS_TO_NOREPLY_ADDRESS = True | ||||||
|  | #TOKENIZED_NOREPLY_EMAIL_ADDRESS = "noreply-{token}@example.com" | ||||||
|  | # Used for noreply emails only if ADD_TOKENS_TO_NOREPLY_ADDRESS=False | ||||||
| #NOREPLY_EMAIL_ADDRESS = 'noreply@example.com' | #NOREPLY_EMAIL_ADDRESS = 'noreply@example.com' | ||||||
|  |  | ||||||
| # Many countries and bulk mailers require certain types of email to display | # Many countries and bulk mailers require certain types of email to display | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user