mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 20:13:46 +00:00 
			
		
		
		
	Compare commits
	
		
			33 Commits
		
	
	
		
	
	| 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. | ||||
| version = '1.9' | ||||
| # 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 | ||||
| # 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 | ||||
| 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 | ||||
|  | ||||
| **Highlights:** | ||||
|   | ||||
| @@ -74,20 +74,117 @@ those providers, Zulip's full-text search will be unavailable. | ||||
| ## Putting the Zulip application behind a reverse proxy | ||||
|  | ||||
| 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 | ||||
| reverse proxy: | ||||
| This section contains notes on the configuration required with | ||||
| 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 | ||||
| `X-Forwarded-For` HTTP header, which is supposed to contain the series | ||||
| of IP addresses the request was forwarded through.  This | ||||
| [nginx code snippet][nginx-proxy-config] will do the right thing, and | ||||
| you can verify your work by looking at `/var/log/zulip/server.log` and | ||||
| checking it has the actual IP addresses of clients, not the IP address | ||||
| of the proxy server. | ||||
| of IP addresses the request was forwarded through.  You can verify | ||||
| your work by looking at `/var/log/zulip/server.log` and checking it | ||||
| has the actual IP addresses of clients, not the IP address of the | ||||
| proxy server. | ||||
|  | ||||
| 2. Ensure your proxy doesn't interfere with Zulip's use of long-polling | ||||
| for real-time push from the server to your users' browsers.  This | ||||
| [nginx code snippet][nginx-proxy-longpolling-config] will do the right thing. | ||||
| 2. Ensure your proxy doesn't interfere with Zulip's use of | ||||
| long-polling for real-time push from the server to your users' | ||||
| browsers.  This [nginx code snippet][nginx-proxy-longpolling-config] | ||||
| does this. | ||||
|  | ||||
| The key configuration options are, for the `/json/events` and | ||||
| `/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 | ||||
|   return occasional 502 errors to clients using Zulip's events API. | ||||
|  | ||||
| 3. The other tricky failure mode with `nginx` reverse proxies is that | ||||
| they can load-balance between the IPv4 and IPv6 addresses for a given | ||||
| hostname.  This can result in mysterious errors that can be quite | ||||
| difficult to debug.  Be sure to declare your `upstreams` in a way that | ||||
| won't do load-balancing unexpectedly (e.g. pointing to a DNS name that | ||||
| you haven't configured 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 | ||||
|  | ||||
| 3. The other tricky failure mode we've seen with `nginx` reverse | ||||
| proxies is that they can load-balance between the IPv4 and IPv6 | ||||
| addresses for a given hostname.  This can result in mysterious errors | ||||
| that can be quite difficult to debug.  Be sure to declare your | ||||
| `upstreams` equivalent in a way that won't do load-balancing | ||||
| unexpectedly (e.g. pointing to a DNS name that you haven't configured | ||||
| with multiple IPs for your Zulip machine; sometimes this happens with | ||||
| IPv6 configuration). | ||||
|   | ||||
| @@ -55,6 +55,30 @@ follows: | ||||
|   providers | ||||
| * 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 | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 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 | ||||
| following commands, replacing the filename with the path to your data | ||||
| export tarball: | ||||
|   | ||||
| @@ -25,9 +25,15 @@ follows: | ||||
|    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). | ||||
|  | ||||
| 1. If you're running Zulip 1.8.1 or newer, you can run `manage.py | ||||
|    register_server` from `/home/zulip/deployments/current`.  This | ||||
|    command will print the registration data it would send to the | ||||
| 1. If you're running Zulip 1.8.1 or newer, you can run the | ||||
|     registration command: | ||||
|     ``` | ||||
|     # 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 | ||||
|    service, and if you accept, register your server.  Otherwise, see | ||||
|    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 | ||||
| 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 | ||||
|  | ||||
| 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 | ||||
|     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 | ||||
|   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. | ||||
|  | ||||
|   * 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. | ||||
|  | ||||
| ## Users and Bots | ||||
| @@ -146,7 +146,7 @@ strength allowed is controlled by two settings in | ||||
|   exceptions: | ||||
|  | ||||
|   * 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 | ||||
|     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 | ||||
| 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 | ||||
|   | ||||
| @@ -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 | ||||
| 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 | ||||
|  | ||||
| ### After Zulip is already installed | ||||
| @@ -80,11 +83,12 @@ To enable the Certbot automation on an already-installed Zulip | ||||
| server, run the following commands: | ||||
| ``` | ||||
| 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 | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -35,25 +35,35 @@ created (e.g. `exampleinc-zulip-uploads`). | ||||
| 1. Comment out the `LOCAL_UPLOADS_DIR` setting in | ||||
| `/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 | ||||
| files to the Zulip server (which will then serve a redirect to the | ||||
| appropriate place in S3), rather than serving them directly. | ||||
|     files to the Zulip server (which will then serve a redirect to the | ||||
|     appropriate place in S3), rather than serving them directly. | ||||
|  | ||||
| With Zulip 1.9.0 and newer, you can do this automatically with the | ||||
| following commands run as root: | ||||
|     With Zulip 1.9.0 and newer, you can do this automatically with the | ||||
|     following commands run as root: | ||||
|  | ||||
| ``` | ||||
| crudini --set /etc/zulip/zulip.conf application_server no_serve_uploads true | ||||
| /home/zulip/deployments/current/scripts/zulip-puppet-apply | ||||
| ``` | ||||
|     ``` | ||||
|     crudini --set /etc/zulip/zulip.conf application_server no_serve_uploads true | ||||
|     /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 | ||||
| `/etc/nginx/sites-available/zulip-enterprise` to comment out the | ||||
| `nginx` configuration block for `/user_avatars` and the `include | ||||
| /etc/nginx/zulip-include/uploads.route` line and then reload the | ||||
| `nginx` service (`service nginx reload`). | ||||
|     With older Zulip, you need to edit | ||||
|     `/etc/nginx/sites-available/zulip-enterprise` to comment out the | ||||
|     `nginx` configuration block for `/user_avatars` and the `include | ||||
|     /etc/nginx/zulip-include/uploads.route` line and then reload the | ||||
|     `nginx` service (`service nginx reload`). | ||||
|  | ||||
| 1. Finally, restart the Zulip server so that your settings changes | ||||
|    take effect | ||||
|   | ||||
| @@ -77,7 +77,7 @@ messages until the migration finishes. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| @@ -100,7 +100,7 @@ Then, set `USING_PGROONGA = False` in `/etc/zulip/settings.py`: | ||||
|  | ||||
| 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 | ||||
| like, you can also remove the `pgroonga = enabled` line in | ||||
|   | ||||
							
								
								
									
										10
									
								
								manage.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								manage.py
									
									
									
									
									
								
							| @@ -1,16 +1,20 @@ | ||||
| #!/usr/bin/env python3 | ||||
| from __future__ import (print_function) | ||||
| import os | ||||
| import sys | ||||
| 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__)) | ||||
| sys.path.append(BASE_DIR) | ||||
| import scripts.lib.setup_path_on_import | ||||
| from scripts.lib.zulip_tools import script_should_not_be_root | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     if 'posix' in os.name and os.geteuid() == 0: | ||||
|         print("manage.py should not be run as root.  Use `su zulip` to drop root.") | ||||
|         sys.exit(1) | ||||
|     script_should_not_be_root() | ||||
|     if (os.access('/etc/zulip/zulip.conf', os.R_OK) and not | ||||
|             os.access('/etc/zulip/zulip-secrets.conf', os.R_OK)): | ||||
|         # The best way to detect running manage.py as another user in | ||||
|   | ||||
| @@ -60,6 +60,21 @@ location / { | ||||
|     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/ { | ||||
|     add_header Access-Control-Allow-Origin *; | ||||
|     add_header Access-Control-Allow-Headers Authorization; | ||||
| @@ -67,7 +82,6 @@ location /api/ { | ||||
|  | ||||
|     include uwsgi_params; | ||||
|     uwsgi_pass django; | ||||
|  | ||||
| } | ||||
|  | ||||
| include /etc/nginx/zulip-include/app.d/*.conf; | ||||
|   | ||||
| @@ -56,6 +56,7 @@ PUPPET_CLASSES="${PUPPET_CLASSES:-zulip::voyager}" | ||||
| VIRTUALENV_NEEDED="${VIRTUALENV_NEEDED:-yes}" | ||||
|  | ||||
| if [ -n "$SELF_SIGNED_CERT" ] && [ -n "$USE_CERTBOT" ]; then | ||||
|     set +x | ||||
|     echo "error: --self-signed-cert and --certbot are incompatible" >&2 | ||||
|     echo >&2 | ||||
|     usage | ||||
| @@ -80,14 +81,16 @@ export LANGUAGE="en_US.UTF-8" | ||||
|  | ||||
| # Check for a supported OS release. | ||||
| apt-get install -y lsb-release sudo | ||||
| os_release="$(lsb_release -sc)" | ||||
| case "$os_release" in | ||||
| os_info="$(lsb_release --short --id --release --codename)" | ||||
| { read -r os_id; read -r os_release; read -r os_codename; } <<< "$os_info" | ||||
|  | ||||
| case "$os_codename" in | ||||
|     trusty|xenial|stretch|bionic) ;; | ||||
|     *) | ||||
|         set +x | ||||
|         cat <<EOF | ||||
|  | ||||
| Unsupported OS release: $os_release | ||||
| Unsupported OS release: $os_codename | ||||
|  | ||||
| Zulip in production is supported only on: | ||||
|  - Debian 9 "stretch" | ||||
| @@ -101,12 +104,30 @@ EOF | ||||
|         exit 1 | ||||
| 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; | ||||
| # otherwise users will find out about insufficient RAM via weird | ||||
| # errors like a segfault running `pip install`. | ||||
| mem_kb=$(head -n1 /proc/meminfo | awk '{print $2}') | ||||
| 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 | ||||
| fi | ||||
|  | ||||
| @@ -141,15 +162,22 @@ EOF | ||||
| fi | ||||
|  | ||||
| apt-get -y dist-upgrade "${APT_OPTIONS[@]}" | ||||
| apt-get install -y \ | ||||
| if ! apt-get install -y \ | ||||
|     puppet git curl wget \ | ||||
|     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 | ||||
|     "$ZULIP_PATH"/scripts/setup/setup-certbot \ | ||||
|         --no-zulip-conf --method=standalone \ | ||||
|         --hostname "$EXTERNAL_HOST" --email "$ZULIP_ADMINISTRATOR" | ||||
|         "$EXTERNAL_HOST" --email "$ZULIP_ADMINISTRATOR" | ||||
| elif [ -n "$SELF_SIGNED_CERT" ]; then | ||||
|     "$ZULIP_PATH"/scripts/setup/generate-self-signed-cert \ | ||||
|         --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 | ||||
|         # `tools/update-prod-static` in order to build the static | ||||
|         # 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 | ||||
|  | ||||
| @@ -319,7 +347,7 @@ if [ -n "$NO_INIT_DB" ]; then | ||||
|  | ||||
|  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 | ||||
|     exit 0 | ||||
| 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 | ||||
| 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")")" | ||||
|  | ||||
|   | ||||
| @@ -11,16 +11,14 @@ os.environ["PYTHONUNBUFFERED"] = "y" | ||||
|  | ||||
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | ||||
| 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.basicConfig(format="%(asctime)s upgrade-zulip: %(message)s", | ||||
|                     level=logging.INFO) | ||||
|  | ||||
| if os.getuid() != 0: | ||||
|     logging.error("Must be run as root.") | ||||
|     sys.exit(1) | ||||
|  | ||||
| if len(sys.argv) != 2: | ||||
|     print(FAIL + "Usage: %s <tarball>" % (sys.argv[0],) + ENDC) | ||||
|     sys.exit(1) | ||||
|   | ||||
| @@ -24,16 +24,14 @@ os.environ["PYTHONUNBUFFERED"] = "y" | ||||
|  | ||||
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) | ||||
| 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.basicConfig(format="%(asctime)s upgrade-zulip-from-git: %(message)s", | ||||
|                     level=logging.INFO) | ||||
|  | ||||
| if os.getuid() != 0: | ||||
|     logging.error("Must be run as root.") | ||||
|     sys.exit(1) | ||||
|  | ||||
| parser = argparse.ArgumentParser() | ||||
| parser.add_argument("refname", help="Git reference, e.g. a branch, tag, or commit ID.") | ||||
| 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" | ||||
|  | ||||
| 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.basicConfig(format="%(asctime)s upgrade-zulip-stage-2: %(message)s", | ||||
|                     level=logging.INFO) | ||||
|  | ||||
| if os.getuid() != 0: | ||||
|     logging.error("Must be run as root.") | ||||
|     sys.exit(1) | ||||
|  | ||||
| # make sure we have appropriate file permissions | ||||
| 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) | ||||
|             return True | ||||
|     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() { | ||||
|     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 | ||||
|     exit 1 | ||||
| } | ||||
| @@ -15,15 +16,10 @@ if [ "$EUID" -ne 0 ]; then | ||||
| fi | ||||
|  | ||||
| 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" | ||||
| while true; do | ||||
|     case "$1" in | ||||
|         --hostname) | ||||
|             DOMAIN="$2" | ||||
|             shift | ||||
|             shift | ||||
|             ;; | ||||
|         --email) | ||||
|             EMAIL="$2" | ||||
|             shift | ||||
| @@ -52,11 +48,19 @@ while true; do | ||||
|             shift | ||||
|             ;; | ||||
|         --) | ||||
|             shift | ||||
|             break | ||||
|             ;; | ||||
|     esac | ||||
| 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 | ||||
|     usage | ||||
| fi | ||||
| @@ -94,7 +98,7 @@ chmod a+x "$CERTBOT_PATH" | ||||
| # Passing --force-interactive suppresses a warning, but also brings up | ||||
| # an annoying prompt we stifle with --no-eff-email. | ||||
| "$CERTBOT_PATH" certonly "${method_args[@]}" \ | ||||
|                 -d "$DOMAIN" -m "$EMAIL" \ | ||||
|                 "${HOSTNAMES[@]}" -m "$EMAIL" \ | ||||
|                 $agree_tos --force-renewal \ | ||||
|                 "${deploy_hook[@]}" \ | ||||
|                 --force-interactive --no-eff-email | ||||
|   | ||||
| @@ -5,7 +5,9 @@ import sys | ||||
| import subprocess | ||||
| import configparser | ||||
| 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 | ||||
| extra_args = sys.argv[1:] | ||||
|   | ||||
| @@ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\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" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\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_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_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.txt:15 | ||||
| #: 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_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/realm_reactivation.html:37 | ||||
| #: templates/zerver/emails/confirm_new_email.source.html:29 | ||||
| #: templates/zerver/emails/confirm_new_email.txt:16 | ||||
| #: templates/zerver/emails/confirm_registration.source.html:27 | ||||
| @@ -1452,6 +1454,7 @@ msgid "Complete registration" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/confirm_registration.html:20 | ||||
| #: templates/zerver/emails/compiled/realm_reactivation.html:29 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1500,88 +1503,80 @@ msgid "Thanks for using Zulip!" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:9 | ||||
| #: 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," | ||||
| msgid "Welcome to Zulip!" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:11 | ||||
| #: 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 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:13 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\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" | ||||
| "        You've created the new Zulip organization <b>%(realm_name)s</b>.\n" | ||||
| "        " | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:19 | ||||
| #: templates/zerver/emails/followup_day1.source.html:18 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:17 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\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 "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:29 | ||||
| #, python-format | ||||
| 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" | ||||
| "    " | ||||
| msgid "Use your LDAP account to login" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:34 | ||||
| #: templates/zerver/emails/followup_day1.source.html:33 | ||||
| #: templates/zerver/emails/followup_day1.txt:17 | ||||
| msgid "Next unread thread" | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:32 | ||||
| msgid "Email:" | ||||
| msgstr "" | ||||
|  | ||||
| #: 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 | ||||
| msgid "" | ||||
| "\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" | ||||
| "    (you'll need these to sign in to the <a href=\"https://zulipchat.com/apps" | ||||
| "\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">mobile " | ||||
| "and desktop</a> apps)\n" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| "    Zulip combines the real-time ease of chat with the threaded " | ||||
| "organization\n" | ||||
| "    of email. Zulip is about productivity—making communication fun and\n" | ||||
| "    easy, while avoiding the distracting and disorganized conversations of\n" | ||||
| "    chatrooms. We hope you love using Zulip as much as we do.\n" | ||||
| "        <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, " | ||||
| "47%%); text-decoration:underline\">Learn more</a> about Zulip, become a pro " | ||||
| "with a few\n" | ||||
| "        <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:61 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:60 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1595,6 +1590,14 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1675,6 +1678,38 @@ msgstr "" | ||||
| msgid "Best," | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1719,6 +1754,11 @@ msgid "" | ||||
| "questions." | ||||
| 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 | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1727,6 +1767,13 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1736,6 +1783,21 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -3324,43 +3386,43 @@ msgstr "" | ||||
| msgid "Wrong subdomain" | ||||
| 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." | ||||
| 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." | ||||
| 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." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:738 | ||||
| #: zerver/views/auth.py:739 | ||||
| msgid "This user is not registered." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:783 | ||||
| #: zerver/views/auth.py:784 | ||||
| msgid "Password auth is disabled in your team." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:789 | ||||
| #: zerver/views/auth.py:790 | ||||
| msgid "This user is not registered; do so from a browser." | ||||
| 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." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:816 | ||||
| #: zerver/views/auth.py:817 | ||||
| msgid "Invalid subdomain" | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:822 | ||||
| #: zerver/views/auth.py:823 | ||||
| msgid "Subdomain required" | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:883 | ||||
| #: zerver/views/auth.py:884 | ||||
| msgid "GOOGLE_CLIENT_ID is not configured" | ||||
| 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입니다.", | ||||
|   "(This user has been deactivated)": "(이 사용자는 비활성화되었습니다.)", | ||||
|   "(no topic)": "(주제없음)", | ||||
|   "1 day": "", | ||||
|   "1 hour": "", | ||||
|   "1 week": "", | ||||
|   "10 minutes": "", | ||||
|   "2 minutes": "", | ||||
|   "1 day": "1 일", | ||||
|   "1 hour": "1 시간", | ||||
|   "1 week": "1 주", | ||||
|   "10 minutes": "10 분", | ||||
|   "2 minutes": "2 분", | ||||
|   "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, 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 members of your organization to mentionable user groups.": "관심있는 사용자 그룹에 단체 구성원을 추가하십시오.", | ||||
|   "Add new default stream": "새 기본 스트림 추가", | ||||
|   "Add option": "", | ||||
|   "Add option": "옵션 추가", | ||||
|   "Add profile field": "", | ||||
|   "Add question": "", | ||||
|   "Add question": "질문 추가", | ||||
|   "Add stream": "스트림 추가", | ||||
|   "Add task": "", | ||||
|   "Add task": "업무 추가", | ||||
|   "Added successfully!": "성공적으로 추가됨!", | ||||
|   "Administrator": "", | ||||
|   "Administrator": "관리자", | ||||
|   "Administrators can always delete any message.": "관리자는 언제든지 모든 메시지를 삭제할 수 있습니다.", | ||||
|   "Admins only": "관리자 만", | ||||
|   "Alert word": "경고문", | ||||
| @@ -149,7 +149,7 @@ | ||||
|   "Default language": "기본 언어", | ||||
|   "Default streams": "기본 스트림", | ||||
|   "Default user settings": "", | ||||
|   "Delete": "", | ||||
|   "Delete": "삭제", | ||||
|   "Delete alert word": "경고문 삭제", | ||||
|   "Delete avatar": "아바타 삭제", | ||||
|   "Delete bot": "봇 삭제", | ||||
|   | ||||
| @@ -44,7 +44,7 @@ | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "it": { | ||||
|     "not_translated": 129, | ||||
|     "not_translated": 0, | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "ja": { | ||||
| @@ -52,7 +52,7 @@ | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "ko": { | ||||
|     "not_translated": 10, | ||||
|     "not_translated": 9, | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "ml": { | ||||
| @@ -92,7 +92,7 @@ | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "zh_Hans": { | ||||
|     "not_translated": 8, | ||||
|     "not_translated": 0, | ||||
|     "total": 140 | ||||
|   }, | ||||
|   "zh_Hant": { | ||||
|   | ||||
| @@ -13,7 +13,7 @@ msgstr "" | ||||
| "Project-Id-Version: Zulip\n" | ||||
| "Report-Msgid-Bugs-To: \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" | ||||
| "Language-Team: Portuguese (http://www.transifex.com/zulip/zulip/language/pt/)\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:10 | ||||
| msgid "All messages" | ||||
| msgstr "Todas mensagens" | ||||
| msgstr "Todas as mensagens" | ||||
|  | ||||
| #: templates/zerver/app/left_sidebar.html:33 | ||||
| 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>__topic_name__</b> as read": "Marcar todas as mensagens em <b>__topic_name__</b> como lidas", | ||||
|   "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", | ||||
|   "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", | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| # Sergey Korablin <s.korablin@gmail.com>, 2018 | ||||
| # Sergey Korablin <s.korablin@gmail.com>, 2018 | ||||
| # Никита Радченко <aygolan@gmail.com>, 2016 | ||||
| # Султонбек Ахмедов <cooltonbek@gmail.com>, 2018 | ||||
| # Султонбек Ахмедов <davlaterra@ya.ru>, 2018 | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Zulip\n" | ||||
|   | ||||
| @@ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: Zulip\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" | ||||
| "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\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_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_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.txt:15 | ||||
| #: 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_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/realm_reactivation.html:37 | ||||
| #: templates/zerver/emails/confirm_new_email.source.html:29 | ||||
| #: templates/zerver/emails/confirm_new_email.txt:16 | ||||
| #: templates/zerver/emails/confirm_registration.source.html:27 | ||||
| @@ -1468,6 +1470,7 @@ msgid "Complete registration" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/confirm_registration.html:20 | ||||
| #: templates/zerver/emails/compiled/realm_reactivation.html:29 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1516,88 +1519,82 @@ msgid "Thanks for using Zulip!" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:9 | ||||
| #: 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," | ||||
| msgid "Welcome to Zulip!" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:11 | ||||
| #: 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 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:13 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\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" | ||||
| "        You've created the new Zulip organization <b>%(realm_name)s</b>.\n" | ||||
| "        " | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:19 | ||||
| #: templates/zerver/emails/followup_day1.source.html:18 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:17 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\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 "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:29 | ||||
| msgid "Use your LDAP account to login" | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:32 | ||||
| #, fuzzy | ||||
| #| msgid "Email" | ||||
| msgid "Email:" | ||||
| msgstr "மின்னஞ்சல்" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:35 | ||||
| #, python-format | ||||
| 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" | ||||
| "    (you'll need these to sign in to the <a href=\"https://zulipchat.com/apps" | ||||
| "\" style=\"color:hsl(164, 42%%, 47%%); text-decoration:underline\">mobile " | ||||
| "and desktop</a> apps)\n" | ||||
| "    " | ||||
| msgstr "" | ||||
|  | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:34 | ||||
| #: templates/zerver/emails/followup_day1.source.html:33 | ||||
| #: templates/zerver/emails/followup_day1.txt:17 | ||||
| msgid "Next unread thread" | ||||
| msgstr "" | ||||
|  | ||||
| #: 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 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:42 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\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" | ||||
| "        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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| "    Zulip combines the real-time ease of chat with the threaded " | ||||
| "organization\n" | ||||
| "    of email. Zulip is about productivity—making communication fun and\n" | ||||
| "    easy, while avoiding the distracting and disorganized conversations of\n" | ||||
| "    chatrooms. We hope you love using Zulip as much as we do.\n" | ||||
| "        <a href=\"%(getting_started_link)s\" style=\"color:hsl(164, 42%%, " | ||||
| "47%%); text-decoration:underline\">Learn more</a> about Zulip, become a pro " | ||||
| "with a few\n" | ||||
| "        <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:61 | ||||
| #: templates/zerver/emails/compiled/followup_day1.html:60 | ||||
| #, python-format | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1611,6 +1608,14 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1691,6 +1696,38 @@ msgstr "" | ||||
| msgid "Best," | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1735,6 +1772,11 @@ msgid "" | ||||
| "questions." | ||||
| 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 | ||||
| msgid "" | ||||
| "\n" | ||||
| @@ -1743,6 +1785,13 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -1752,6 +1801,21 @@ msgid "" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| msgid "" | ||||
| @@ -3342,43 +3406,43 @@ msgstr "" | ||||
| msgid "Wrong subdomain" | ||||
| 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." | ||||
| 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." | ||||
| 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." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:738 | ||||
| #: zerver/views/auth.py:739 | ||||
| msgid "This user is not registered." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:783 | ||||
| #: zerver/views/auth.py:784 | ||||
| msgid "Password auth is disabled in your team." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:789 | ||||
| #: zerver/views/auth.py:790 | ||||
| msgid "This user is not registered; do so from a browser." | ||||
| 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." | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:816 | ||||
| #: zerver/views/auth.py:817 | ||||
| msgid "Invalid subdomain" | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:822 | ||||
| #: zerver/views/auth.py:823 | ||||
| msgid "Subdomain required" | ||||
| msgstr "" | ||||
|  | ||||
| #: zerver/views/auth.py:883 | ||||
| #: zerver/views/auth.py:884 | ||||
| msgid "GOOGLE_CLIENT_ID is not configured" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -14,8 +14,8 @@ msgstr "" | ||||
| "Project-Id-Version: Zulip\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2018-11-03 00:32+0000\n" | ||||
| "PO-Revision-Date: 2018-11-03 00:04+0000\n" | ||||
| "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" | ||||
| "PO-Revision-Date: 2018-11-08 07:30+0000\n" | ||||
| "Last-Translator: longjiang li <cqlilon@live.com>\n" | ||||
| "Language-Team: Chinese Simplified (http://www.transifex.com/zulip/zulip/language/zh-Hans/)\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| @@ -318,7 +318,7 @@ msgstr "保存为草稿" | ||||
|  | ||||
| #: templates/zerver/app/compose.html:20 | ||||
| msgid "New message" | ||||
| msgstr "" | ||||
| msgstr "新消息" | ||||
|  | ||||
| #: templates/zerver/app/compose.html:27 templates/zerver/app/compose.html:28 | ||||
| msgid "New topic" | ||||
| @@ -1380,7 +1380,7 @@ msgid "" | ||||
| "    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" | ||||
| "    " | ||||
| 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_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" | ||||
| "    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/find_team.source.html:8 | ||||
| @@ -1511,7 +1511,7 @@ msgid "" | ||||
| "    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" | ||||
| "    " | ||||
| 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/followup_day1.source.html:18 | ||||
| @@ -1528,7 +1528,7 @@ msgid "" | ||||
| "    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 "\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/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" | ||||
| "    %(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 | ||||
| msgid "" | ||||
| @@ -1576,7 +1576,7 @@ msgid "" | ||||
| "    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" | ||||
| "    " | ||||
| 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 | ||||
| #, python-format | ||||
| @@ -1601,7 +1601,7 @@ msgid "" | ||||
| "Feel free to give us a shout at <a href=\"mailto:%(support_email)s\" " | ||||
| "style=\"color:hsl(164, 42%%, 47%%); text-" | ||||
| "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_reminder.html:23 | ||||
| @@ -1625,7 +1625,7 @@ msgid "" | ||||
| "href=\"mailto:%(referrer_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | ||||
| "decoration:underline\">%(referrer_email)s</a>) wants you to join them on " | ||||
| "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 | ||||
| #, python-format | ||||
| @@ -1633,7 +1633,7 @@ msgid "" | ||||
| "We're here for you at <a href=\"mailto:%(support_email)s\" " | ||||
| "style=\"color:hsl(164, 42%%, 47%%); text-" | ||||
| "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/notify_change_in_email.source.html:8 | ||||
| @@ -1651,7 +1651,7 @@ msgid "" | ||||
| "change, please contact us immediately at <a " | ||||
| "href=\"mailto:%(support_email)s\" style=\"color:hsl(164, 42%%, 47%%); text-" | ||||
| "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/notify_change_in_email.source.html:13 | ||||
| @@ -2371,7 +2371,7 @@ msgstr "必须是组织管理员" | ||||
|  | ||||
| #: zerver/decorator.py:142 | ||||
| msgid "Must be a billing administrator or an organization administrator" | ||||
| msgstr "" | ||||
| msgstr "必须是账单管理员或者组织管理员" | ||||
|  | ||||
| #: zerver/decorator.py:224 | ||||
| msgid "Invalid subdomain for push notifications bouncer" | ||||
| @@ -3011,7 +3011,7 @@ msgstr "名称中有无效字符!" | ||||
|  | ||||
| #: zerver/lib/users.py:39 | ||||
| msgid "Name is already in use!" | ||||
| msgstr "" | ||||
| msgstr "用户名已被占用" | ||||
|  | ||||
| #: zerver/lib/users.py:44 zerver/views/users.py:283 zerver/views/users.py:455 | ||||
| msgid "Bad name or username" | ||||
| @@ -3736,7 +3736,7 @@ msgstr "无法停用唯一的社群管理员" | ||||
|  | ||||
| #: zerver/views/users.py:97 | ||||
| msgid "Guests cannot be organization administrators" | ||||
| msgstr "" | ||||
| msgstr "访客不能是组织管理员" | ||||
|  | ||||
| #: zerver/views/users.py:101 | ||||
| msgid "Cannot remove the only organization administrator" | ||||
|   | ||||
| @@ -292,14 +292,14 @@ | ||||
|   "Mobile notifications": "移动端通知", | ||||
|   "Mobile notifications always (even when online)": "移动端通知(在线)", | ||||
|   "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周", | ||||
|   "Mute stream": "静音频道", | ||||
|   "Mute the stream <b>__stream.name__</b>": "频道"<b>__stream.name__</b>"开启免打扰", | ||||
|   "Mute the topic <b>__subject__</b>": "话题"<b>__subject__</b>"开启免打扰", | ||||
|   "Mute the topic <b>__topic_name__</b>": "话题"<b>__topic_name__</b>"开启免打扰", | ||||
|   "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": "已静音主题", | ||||
|   "N": "N", | ||||
|   "Name": "名称", | ||||
| @@ -316,14 +316,14 @@ | ||||
|   "New conversation": "新对话", | ||||
|   "New email": "新电子邮件", | ||||
|   "New full name": "新全名", | ||||
|   "New members can only see messages sent after they join.": "", | ||||
|   "New members can view complete message history.": "", | ||||
|   "New members can only see messages sent after they join.": "新成员只能看到加入后发送的消息。", | ||||
|   "New members can view complete message history.": "新成员可以查看完整的消息历史。", | ||||
|   "New password": "新密码", | ||||
|   "New password is too weak": "", | ||||
|   "New password is too weak": "新密码太弱", | ||||
|   "New private message": "写私信", | ||||
|   "New stream message": "写消息", | ||||
|   "New stream notifications:": "", | ||||
|   "New task": "", | ||||
|   "New stream notifications:": "新频道通知:", | ||||
|   "New task": "新任务", | ||||
|   "New topic": "新话题", | ||||
|   "New user notifications:": "新用户通知", | ||||
|   "Next week": "下周", | ||||
| @@ -334,7 +334,7 @@ | ||||
|   "No default streams match you current filter.": "没有默认频道可以匹配你当前的过滤器。", | ||||
|   "No description.": "没有描述信息。", | ||||
|   "No drafts.": "没有草稿", | ||||
|   "No invites match your current filter.": "", | ||||
|   "No invites match your current filter.": "没有邀请匹配当前过滤器。", | ||||
|   "No more topics.": "没有更多话题。", | ||||
|   "No restrictions": "无限制", | ||||
|   "No users match your current filter.": "没有匹配到用户在你的筛选器中。", | ||||
| @@ -346,25 +346,25 @@ | ||||
|   "Notifications stream changed!": "频道通知已更改!", | ||||
|   "Notifications stream disabled!": "频道通知已禁用!", | ||||
|   "Old password": "旧密码", | ||||
|   "On __last_active__": "", | ||||
|   "On __last_active_date__": "", | ||||
|   "On __last_active__": "在__last_active__", | ||||
|   "On __last_active_date__": "在__last_active_date__", | ||||
|   "Only organization administrators can add bots to this organization": "只有组织管理员可以将机器人添加到该组织", | ||||
|   "Only organization administrators can add custom emoji in this organization.": "只有社群管理员才能自定义表情在这个社群中。", | ||||
|   "Only organization administrators can add generic bots": "只有组织管理员可以添加通用机器人", | ||||
|   "Only organization administrators can edit these settings.": "只有社群管理员才能编辑这些设置。", | ||||
|   "Only organization administrators can post.": "", | ||||
|   "Only organization admins are allowed to post to this stream.": "", | ||||
|   "Only organization administrators can post.": "只有组织管理员才能发布。", | ||||
|   "Only organization admins are allowed to post to this stream.": "只有组织管理员可以发布到这个频道。", | ||||
|   "Optional": "可选设置", | ||||
|   "Organization": "社群", | ||||
|   "Organization administrators can change this in the organization settings.": "", | ||||
|   "Organization administrators can change this in the organization settings.": "组织管理员可以在组织设置中更改此设置。", | ||||
|   "Organization avatar": "社群头像", | ||||
|   "Organization description": "", | ||||
|   "Organization description": "组织描述", | ||||
|   "Organization name": "社群名称", | ||||
|   "Organization permissions": "社群许可", | ||||
|   "Organization profile": "社群资料", | ||||
|   "Organization settings": "社区设置", | ||||
|   "Other notification settings": "", | ||||
|   "Other permissions": "", | ||||
|   "Other notification settings": "其他通知设置", | ||||
|   "Other permissions": "其他权限", | ||||
|   "Outgoing webhook message format": "送出的webhook消息格式", | ||||
|   "Owner": "所有者", | ||||
|   "Password": "密码", | ||||
| @@ -376,96 +376,96 @@ | ||||
|   "Pin stream to top of left sidebar": "钉住频道在左侧栏的顶部", | ||||
|   "Please just upload one file.": "请上传一个文件", | ||||
|   "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 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 name": "防止用户更改名称", | ||||
|   "Preview profile": "", | ||||
|   "Preview profile": "预览资料", | ||||
|   "Private messages and @-mentions": "私信和@提醒", | ||||
|   "Profile": "", | ||||
|   "Profile field settings": "", | ||||
|   "Question": "", | ||||
|   "Profile": "资料", | ||||
|   "Profile field settings": "资料字段设置", | ||||
|   "Question": "问题", | ||||
|   "Quote and reply": "引用并回复", | ||||
|   "Reactivate": "启用", | ||||
|   "Reactivate bot": "重启机器人", | ||||
|   "Regular expression": "正则表达式", | ||||
|   "Remind me about this": "", | ||||
|   "Reminder not set!": "", | ||||
|   "Reminder set!": "", | ||||
|   "Remind me about this": "提醒我", | ||||
|   "Reminder not set!": "提醒没有设置!", | ||||
|   "Reminder set!": "提醒已设置!", | ||||
|   "Remove": "移除", | ||||
|   "Remove from default": "取消默认频道", | ||||
|   "Reply (r)": "", | ||||
|   "Reply (r)": "回复(r)", | ||||
|   "Reply mentioning user": "回复提到用户", | ||||
|   "Require topics in stream messages": "频道消息中所需的主题", | ||||
|   "Resend": "", | ||||
|   "Resend invitation to <span class=\"email\"></span>": "", | ||||
|   "Resend now": "", | ||||
|   "Resending encountered an error. Please reload and try again.": "", | ||||
|   "Resend": "重新发送", | ||||
|   "Resend invitation to <span class=\"email\"></span>": "重新发送邀请到<span class=\"email\"></span>", | ||||
|   "Resend now": "立即重新发送", | ||||
|   "Resending encountered an error. Please reload and try again.": "重发是发生错误。请刷新后再试", | ||||
|   "Restore draft": "恢复草稿", | ||||
|   "Restrict email domains of new users?": "", | ||||
|   "Restrict posting to organization administrators": "", | ||||
|   "Restrict to a list of domains": "", | ||||
|   "Restrict email domains of new users?": "限制新用户的电子邮件域?", | ||||
|   "Restrict posting to organization administrators": "限制发布到组织管理员", | ||||
|   "Restrict to a list of domains": "限制到一个域列表", | ||||
|   "Retry": "重试", | ||||
|   "Revoke": "", | ||||
|   "Revoke invitation to <span class=\"email\"></span>": "", | ||||
|   "Revoke now": "", | ||||
|   "Role": "", | ||||
|   "Revoke": "撤销", | ||||
|   "Revoke invitation to <span class=\"email\"></span>": "撤销发送给<span class=\"email\"></span>的邀请", | ||||
|   "Revoke now": "立即撤销", | ||||
|   "Role": "角色", | ||||
|   "Save": "保存", | ||||
|   "Save changes": "保存修改", | ||||
|   "Save failed": "", | ||||
|   "Saved": "", | ||||
|   "Saved. Please <a class='reload_link'>reload</a> for the change to take effect.": "", | ||||
|   "Saving": "", | ||||
|   "Save failed": "保存失败", | ||||
|   "Saved": "已保存", | ||||
|   "Saved. Please <a class='reload_link'>reload</a> for the change to take effect.": "已保存。请<a class='reload_link'>刷新</a>使更改生效", | ||||
|   "Saving": "保存中", | ||||
|   "Search": "搜索", | ||||
|   "Search operators": "搜索管理者", | ||||
|   "Search results": "搜索结果", | ||||
|   "Search subscribers": "搜索订阅者", | ||||
|   "Search uploads...": "搜索已上传的文件", | ||||
|   "See the rest of this message": "查看其余内容", | ||||
|   "Select date and time": "", | ||||
|   "Select date and time": "选择日期和时间", | ||||
|   "Select default language": "选择默认语言", | ||||
|   "Send digest emails when I'm away": "", | ||||
|   "Send email notifications for new logins to my account": "", | ||||
|   "Send emails introducing Zulip to new users": "", | ||||
|   "Send digest emails when I'm away": "当我离线时发送摘要邮件", | ||||
|   "Send email notifications for new logins to my account": "我的帐户进行新的登录时发送电子邮件通知", | ||||
|   "Send emails introducing Zulip to new users": "向新用户发送介绍Zulip的电子邮件", | ||||
|   "Send private message": "发送私有消息", | ||||
|   "Sent!": "", | ||||
|   "Sent!": "已发送!", | ||||
|   "Settings": "设置", | ||||
|   "Setup": "", | ||||
|   "Setup two factor authentication": "", | ||||
|   "Show counts for starred messages": "", | ||||
|   "Setup": "设置", | ||||
|   "Setup two factor authentication": "设置双重认证", | ||||
|   "Show counts for starred messages": "显示星标消息的数量", | ||||
|   "Show previews of linked websites": "显示链接网站的预览", | ||||
|   "Show previews of uploaded and linked images": "显示上传文件链接的图像预览", | ||||
|   "Show/change your API key": "显示/修改您的 API Key", | ||||
|   "Signup notifications stream changed!": "", | ||||
|   "Signup notifications stream disabled!": "", | ||||
|   "Signup notifications stream changed!": "频道注册通知已变更", | ||||
|   "Signup notifications stream disabled!": "频道注册通知已禁用", | ||||
|   "Size": "大小", | ||||
|   "Slack compatible": "高度兼容", | ||||
|   "Slack's outgoing webhooks": "", | ||||
|   "Slack's outgoing webhooks": "Slack发送的webhook", | ||||
|   "Sorry, the file was too large.": "对不起,文件太大了。", | ||||
|   "Star": "星标", | ||||
|   "Stream": "频道", | ||||
|   "Stream color": "频道颜色", | ||||
|   "Stream created recently": "", | ||||
|   "Stream created recently": "最近创建的频道", | ||||
|   "Stream creation": "频道创建", | ||||
|   "Stream description": "频道描述", | ||||
|   "Stream description (optional)": "频道描述(可选)", | ||||
|   "Stream membership": "频道用户", | ||||
|   "Stream messages": "频道消息", | ||||
|   "Stream name": "频道名称", | ||||
|   "Stream permissions": "", | ||||
|   "Stream permissions": "频道权限", | ||||
|   "Stream settings": "频道设置", | ||||
|   "Stream successfully created!": "", | ||||
|   "Stream successfully created!": "频道创建成功", | ||||
|   "Streams": "频道", | ||||
|   "Submit": "", | ||||
|   "Submit": "提交", | ||||
|   "Subscribe": "订阅", | ||||
|   "Subscribed": "已订阅", | ||||
|   "Subscribed successfully!": "", | ||||
|   "Subscriber count": "", | ||||
|   "Subscribed successfully!": "订阅成功", | ||||
|   "Subscriber count": "订阅者数量", | ||||
|   "Subscribers": "订阅者", | ||||
|   "Task already exists": "", | ||||
|   "Text": "", | ||||
|   "Task already exists": "任务已经存在", | ||||
|   "Text": "文本", | ||||
|   "The email body will become the Zulip message": "电子邮件正文将成为Zulip消息", | ||||
|   "The email subject will become the Zulip topic": "电子邮件正文将成为Zulip话题", | ||||
|   "The email will be forwarded to this stream": "邮件将会转发到这个频道中", | ||||
| @@ -474,27 +474,27 @@ | ||||
|   "The stream description has been updated!": "频道描述信息已更新", | ||||
|   "The stream has been renamed!": "频道重命名成功!", | ||||
|   "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.": "这些用户的密码会被从系统中清除,他们的机器人用户也会被关闭。", | ||||
|   "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>.": "", | ||||
|   "This action is permanent and cannot be undone. All users will permanently lose access to their Zulip accounts.": "", | ||||
|   "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-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.": "", | ||||
|   "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>.": "这些设置在<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.": "这项操作是永久且不可撤销的。所有用户将永久失去对Zulip账户的访问权限。", | ||||
|   "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.": "这是一个<span class=\"fa fa-lock\" aria-hidden=\"true\"></span><b>私有频道</b>。仅有邀请的用户可以对该频道进行访问,该频道的用户也可以邀请其它用户。", | ||||
|   "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 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 zone": "时区", | ||||
|   "Time's up!": "时间到了!", | ||||
|   "Today": "今日", | ||||
|   "Toggle subscription": "触发订阅", | ||||
|   "Tomorrow": "", | ||||
|   "Tomorrow": "明天", | ||||
|   "Topic": "话题", | ||||
|   "Topic editing only": "只能主题编辑", | ||||
|   "Try again": "再试一次", | ||||
|   "Two factor authentication": "", | ||||
|   "Type": "", | ||||
|   "Two factor authentication": "双重认证", | ||||
|   "Type": "类型", | ||||
|   "URL format string": "URL格式", | ||||
|   "Un-collapse": "展开", | ||||
|   "Unable to upload that many files at once.": "无法一次上传这么多的文件。", | ||||
| @@ -507,35 +507,35 @@ | ||||
|   "Unpin stream <b>__stream.name__</b> from top": "取消频道\"<b>__stream.name__</b>\"置顶", | ||||
|   "Unstar": "取消星标", | ||||
|   "Unsubscribe": "退订", | ||||
|   "Unsubscribed successfully!": "", | ||||
|   "Up to N minutes after posting": "", | ||||
|   "Up to __time_limit__ after posting": "", | ||||
|   "Unsubscribed successfully!": "退订成功", | ||||
|   "Up to N minutes after posting": "发布后N分钟", | ||||
|   "Up to __time_limit__ after posting": "发布后__time_limit__", | ||||
|   "Update successful: Subdomains allowed for __domain__": "更新成功:允许新的域名于__domain__", | ||||
|   "Update successful: Subdomains no longer allowed for __domain__": "更新成功:不在允许这个域名 __domain__", | ||||
|   "Updated settings!": "", | ||||
|   "Updated settings!": "更新设置", | ||||
|   "Updated successfully!": "更新成功!", | ||||
|   "Upload avatar": "上传头像", | ||||
|   "Upload icon": "上传图标", | ||||
|   "Upload image or GIF": "", | ||||
|   "Upload image or GIF": "上传图片", | ||||
|   "Upload new avatar": "上传一个新头像", | ||||
|   "Upload new icon": "上传新图标", | ||||
|   "Uploaded files": "已上传文件", | ||||
|   "Uploading icon.": "图标上传中", | ||||
|   "Uploading\u2026": "上传", | ||||
|   "User already subscribed.": "", | ||||
|   "User already subscribed.": "用户已经订阅", | ||||
|   "User avatar": "用户头像", | ||||
|   "User group added!": "", | ||||
|   "User group added!": "用户组已添加", | ||||
|   "User groups": "用户组", | ||||
|   "User identity": "用户标识", | ||||
|   "User is already not subscribed.": "", | ||||
|   "User is already not subscribed.": "用户没有订阅", | ||||
|   "User list on left sidebar in narrow windows": "窗口右侧变懒的用户列表", | ||||
|   "User role": "", | ||||
|   "User role": "用户角色", | ||||
|   "User settings": "用户设置", | ||||
|   "User(s) invited successfully.": "", | ||||
|   "User(s) invited successfully.": "用户邀请成功", | ||||
|   "Username": "用户名", | ||||
|   "Username (a-z, 0-9, and dashes only)": "", | ||||
|   "Users can edit the topic of any message": "", | ||||
|   "Video chat provider": "", | ||||
|   "Username (a-z, 0-9, and dashes only)": "用户名(字母、数字和下划线)", | ||||
|   "Users can edit the topic of any message": "用户可以编辑消息的主题", | ||||
|   "Video chat provider": "视频聊天提供者", | ||||
|   "View edit history": "显示编辑历史 ", | ||||
|   "View file": "显示文件", | ||||
|   "View messages sent": "显示已发送消息", | ||||
| @@ -543,58 +543,58 @@ | ||||
|   "View source": "显示源", | ||||
|   "View source / Edit topic": "查看源 / 编辑主题", | ||||
|   "View stream": "显示频道", | ||||
|   "View user profile": "", | ||||
|   "View your profile": "", | ||||
|   "Visual desktop notifications": "", | ||||
|   "Warning: <strong>__stream_name__</strong> is a private stream.": "", | ||||
|   "Who can add bots": "", | ||||
|   "Who can add custom emoji": "", | ||||
|   "Who can create streams": "", | ||||
|   "View user profile": "查看用户资料", | ||||
|   "View your profile": "查看我的资料", | ||||
|   "Visual desktop notifications": "可视桌面通知", | ||||
|   "Warning: <strong>__stream_name__</strong> is a private stream.": "警告:<strong>__stream_name__</strong>是私有频道", | ||||
|   "Who can add bots": "谁能添加机器人", | ||||
|   "Who can add custom emoji": "谁能添加自定义表情", | ||||
|   "Who can create streams": "谁能创建频道", | ||||
|   "Working\u2026": "进行中", | ||||
|   "Yes": "是", | ||||
|   "Yes, delete this stream": "是的,删除该频道", | ||||
|   "Yes, send": "是的,发送", | ||||
|   "Yes, subscribe __count__ users!": "确定,订阅 __count__  用户!", | ||||
|   "Yes. Members and admins can send invitations.": "", | ||||
|   "Yes. Only admins can send invitations.": "", | ||||
|   "Yes. Members and admins can send invitations.": "是的,普通成员和管理员可以发送邀请", | ||||
|   "Yes. Only admins can send invitations.": "是的,只有管理员可以发送邀请", | ||||
|   "Yesterday": "昨天", | ||||
|   "You and __display_reply_to__": "您和__display_reply_to__", | ||||
|   "You and __recipients__": "你和 __recipients__", | ||||
|   "You are not currently subscribed to this 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 cannot create a stream with no subscribers!": "", | ||||
|   "You cannot create a stream with no subscribers!": "创建频道时必须有订阅者", | ||||
|   "You have no active bots.": "你没有可用的机器人。", | ||||
|   "You have no inactive bots.": "你没有不可用的机器人。", | ||||
|   "You have not muted any topics yet.": "你还没有任何静音的话题", | ||||
|   "You have not uploaded any files.": "目前没有上传任何文件。", | ||||
|   "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 subscribed to 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 account": "你的账户", | ||||
|   "Your bots": "你的机器人", | ||||
|   "Your reminder note is empty!": "", | ||||
|   "Your reminder note is empty!": "您没有提醒事项", | ||||
|   "[Condense this message]": "[收起消息]", | ||||
|   "[Configure]": "", | ||||
|   "[Configure]": "[配置]", | ||||
|   "[Disable]": "[禁用]", | ||||
|   "[More...]": "[更多...]", | ||||
|   "__hours__ hours ago": "", | ||||
|   "__hours__ hours ago": "__hours__小时以前", | ||||
|   "__minutes__ min to edit": "__minutes__分钟内完成编辑", | ||||
|   "__minutes__ minutes ago": "", | ||||
|   "__minutes__ minutes ago": "__minutes__分钟以前", | ||||
|   "__seconds__ sec to edit": "__seconds__秒内完成编辑", | ||||
|   "__starred_status__ this message": "__starred_status__这个消息", | ||||
|   "__wildcard_mention_token__ (Notify stream)": "", | ||||
|   "__wildcard_mention_token__ (Notify stream)": "__wildcard_mention_token__ (通知频道)", | ||||
|   "and": "来", | ||||
|   "cookie": "cookie", | ||||
|   "in 1 hour": "1小时内", | ||||
|   "in 20 minutes": "20分钟内", | ||||
|   "in 3 hours": "3小时内", | ||||
|   "leafy green vegetable": "", | ||||
|   "marketing": "", | ||||
|   "leafy green vegetable": "绿叶蔬菜", | ||||
|   "marketing": "销售", | ||||
|   "more conversations": "更多会话", | ||||
|   "more topics": "更多话题" | ||||
| } | ||||
| @@ -33,9 +33,10 @@ organization first. | ||||
|  | ||||
| ### Import into a self-hosted Zulip server | ||||
|  | ||||
| Because the import tool is very new, you will 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](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). | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -65,6 +65,11 @@ organization first. | ||||
|  | ||||
| ### 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 | ||||
| upgrade your Zulip server to the latest `master` branch, | ||||
| using [upgrade-zulip-from-git][upgrade-zulip-from-git]. | ||||
|   | ||||
| @@ -35,9 +35,10 @@ organization first. | ||||
|  | ||||
| ### Import into a self-hosted Zulip server | ||||
|  | ||||
| Because the import tool is very new, you will 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](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). | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -770,6 +770,10 @@ def build_custom_checkers(by_lang): | ||||
|          'include_only': set(['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]', | ||||
|          'include_only': set(['README.md', 'CONTRIBUTING.md']), | ||||
|          '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 | ||||
| # 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. | ||||
|     """ | ||||
|  | ||||
|     def get_avatar(avatar_upload_list: List[str]) -> int: | ||||
|         avatar_url = avatar_upload_list[0] | ||||
|     def get_avatar(avatar_upload_item: List[str]) -> int: | ||||
|         avatar_url = avatar_upload_item[0] | ||||
|  | ||||
|         image_path = os.path.join(avatar_dir, avatar_original_list[1]) | ||||
|         original_image_path = os.path.join(avatar_dir, avatar_original_list[2]) | ||||
|         image_path = os.path.join(avatar_dir, avatar_upload_item[1]) | ||||
|         original_image_path = os.path.join(avatar_dir, avatar_upload_item[2]) | ||||
|  | ||||
|         response = requests.get(avatar_url + size_url_suffix, stream=True) | ||||
|         with open(image_path, 'wb') as image_file: | ||||
|   | ||||
| @@ -599,6 +599,7 @@ def import_uploads_s3(bucket_name: str, import_dir: Path, processing_avatars: bo | ||||
|             user_profile = get_user_profile_by_id(user_profile_id) | ||||
|             key.set_metadata("user_profile_id", str(user_profile.id)) | ||||
|  | ||||
|         if 'last_modified' in record: | ||||
|             key.set_metadata("orig_last_modified", record['last_modified']) | ||||
|         key.set_metadata("realm_id", str(record['realm_id'])) | ||||
|  | ||||
|   | ||||
| @@ -114,7 +114,7 @@ def send_apple_push_notification(user_id: int, devices: List[DeviceToken], | ||||
|  | ||||
|     client = get_apns_client()  # type: APNsClient | ||||
|     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).") | ||||
|         return | ||||
|  | ||||
| @@ -186,7 +186,7 @@ def send_android_push_notification_to_user(user_profile: UserProfile, data: Dict | ||||
| def send_android_push_notification(devices: List[DeviceToken], data: Dict[str, Any], | ||||
|                                    remote: bool=False) -> None: | ||||
|     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") | ||||
|         return | ||||
|     reg_ids = [device.token for device in devices] | ||||
| @@ -429,6 +429,12 @@ def push_notifications_enabled() -> bool: | ||||
|         return True | ||||
|     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: | ||||
|     """ | ||||
|     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) | ||||
|     (message, user_message) = access_message(user_profile, missed_message['message_id']) | ||||
|     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 | ||||
|         # put in the queue; maybe we should centralize this logic with | ||||
|   | ||||
| @@ -431,7 +431,7 @@ class S3UploadBackend(ZulipUploadBackend): | ||||
|         bucket_name = settings.S3_AVATAR_BUCKET | ||||
|         conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY) | ||||
|         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() | ||||
|  | ||||
|         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 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.""" | ||||
|  | ||||
|     def add_arguments(self, parser: ArgumentParser) -> None: | ||||
|         parser.add_argument('realm', metavar='<realm>', type=str, | ||||
|                             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) | ||||
|         self.add_realm_args(parser, required=True) | ||||
|  | ||||
|     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() | ||||
|  | ||||
|         if users: | ||||
| @@ -32,4 +26,5 @@ class Command(BaseCommand): | ||||
|         else: | ||||
|             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_get.return_value = None | ||||
|             self.send() | ||||
|             mock_logging.warning.assert_called_once_with( | ||||
|             mock_logging.debug.assert_called_once_with( | ||||
|                 "APNs: Dropping a notification because nothing configured.  " | ||||
|                 "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: | ||||
|         with self.mock_apns() as mock_apns, \ | ||||
| @@ -1154,11 +1159,11 @@ class GCMTest(PushNotificationTest): | ||||
|         return data | ||||
|  | ||||
| class GCMNotSetTest(GCMTest): | ||||
|     @mock.patch('logging.warning') | ||||
|     def test_gcm_is_none(self, mock_warning: mock.MagicMock) -> None: | ||||
|     @mock.patch('logging.debug') | ||||
|     def test_gcm_is_none(self, mock_debug: mock.MagicMock) -> None: | ||||
|         apn.gcm = None | ||||
|         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 " | ||||
|             "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'), | ||||
|         'scope': 'profile email', | ||||
|         'state': csrf_state, | ||||
|         'prompt': 'select_account', | ||||
|     } | ||||
|     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.timestamp import timestamp_to_datetime | ||||
| 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, \ | ||||
|     do_update_user_activity, do_update_user_activity_interval, do_update_user_presence, \ | ||||
|     internal_send_message, check_send_message, extract_recipients, \ | ||||
| @@ -352,6 +353,13 @@ class MissedMessageSendingWorker(EmailSendingWorker):  # nocoverage | ||||
|  | ||||
| @assign_queue('missedmessage_mobile_notifications') | ||||
| 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: | ||||
|         if data.get("type", "add") == "remove": | ||||
|             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 | ||||
| # emails.  Messages sent to this address could contain sensitive user | ||||
| # data and should not be delivered anywhere.  The default is | ||||
| # e.g. noreply@zulip.example.com (if EXTERNAL_HOST is | ||||
| # zulip.example.com). | ||||
| # e.g. noreply-{random_token}@zulip.example.com (if EXTERNAL_HOST is | ||||
| # 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' | ||||
|  | ||||
| # Many countries and bulk mailers require certain types of email to display | ||||
|   | ||||
		Reference in New Issue
	
	Block a user