diff --git a/.gitignore b/.gitignore index f58f8f83f..2c2eb3010 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ src Read the Docs config.rst +/.project +/.pydevproject + diff --git a/.travis.yml b/.travis.yml index fa10c6c90..c88bc8595 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - 3.5.1 # Set to 3.5.1 since travis has not yet included as default for 3.5 - 2.7 -sudo: false +sudo: required env: global: @@ -37,6 +37,8 @@ before_install: install: - pip install -f travis-wheels/wheelhouse file://$PWD#egg=notebook[test] + - wget https://github.com/jgm/pandoc/releases/download/1.19.1/pandoc-1.19.1-1-amd64.deb && sudo dpkg -i pandoc-1.19.1-1-amd64.deb + script: - 'if [[ $GROUP == js* ]]; then travis_retry python -m notebook.jstest ${GROUP:3}; fi' diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a3a8c3b53..0c0a61fc6 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -24,22 +24,20 @@ Installing Node.js and npm ^^^^^^^^^^^^^^^^^^^^^^^^^^ Building the Notebook from its GitHub source code requires some tools to -create and minify JavaScript components and the CSS. -Namely, that's Node.js and Node's package manager, ``npm``. +create and minify JavaScript components and the CSS, +specifically Node.js and Node's package manager, ``npm``. +It should be node version ≥ 6.0. If you use ``conda``, you can get them with:: - conda install -c javascript nodejs + conda install -c conda-forge nodejs If you use `Homebrew `_ on Mac OS X:: brew install node -For Debian/Ubuntu systems, you should use the ``nodejs-legacy`` package instead -of the ``node`` package:: - - sudo apt-get update - sudo apt-get install nodejs-legacy npm +Installation on Linux may vary, but be aware that the `nodejs` or `npm` packages +included in the system package repository may be too old to work properly. You can also use the installer from the `Node.js website `_. @@ -50,14 +48,13 @@ Installing the Jupyter Notebook Once you have installed the dependencies mentioned above, use the following steps:: - pip install setuptools pip --upgrade --user + pip install --upgrade setuptools pip git clone https://github.com/jupyter/notebook cd notebook - pip install -e . --user + pip install -e . -If you want the development environment to be available for all users of your -system (assuming you have the necessary rights) or if you are installing in a -virtual environment, just drop the ``--user`` option. +If you are using a system-wide Python installation and you only want to install the notebook for you, +you can add ``--user`` to the install commands. Once you have done this, you can launch the master branch of Jupyter notebook from any directory in your system with:: @@ -108,7 +105,7 @@ Python Tests Install dependencies:: - pip install -e .[test] --user + pip install -e .[test] To run the Python tests, use:: @@ -124,7 +121,7 @@ JavaScript Tests To run the JavaScript tests, you will need to have PhantomJS and CasperJS installed:: - npm install -g casperjs phantomjs@1.9.18 + npm install -g casperjs phantomjs-prebuilt Then, to run the JavaScript tests:: @@ -157,10 +154,9 @@ containing all the necessary packages (except pandoc), use:: .. _conda environment: http://conda.pydata.org/docs/using/envs.html#use-environment-from-file -If you want to install the necessary packages with ``pip`` instead, use -(omitting --user if working in a virtual environment):: +If you want to install the necessary packages with ``pip`` instead:: - pip install -r docs/doc-requirements.txt --user + pip install -r docs/doc-requirements.txt Once you have installed the required packages, you can build the docs with:: diff --git a/MANIFEST.in b/MANIFEST.in index 6e377a73a..63f216e0b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include COPYING.md -include CONTRIBUTING.md +include CONTRIBUTING.rst include README.md include package.json include bower.json diff --git a/bower.json b/bower.json index ee6d222f3..52ca303be 100644 --- a/bower.json +++ b/bower.json @@ -5,19 +5,25 @@ "backbone": "components/backbone#~1.2", "bootstrap": "components/bootstrap#~3.3", "bootstrap-tour": "0.9.0", - "codemirror": "components/codemirror#~5.22.2", + "codemirror": "components/codemirror#~5.27", "es6-promise": "~1.0", - "font-awesome": "components/font-awesome#~4.2.0", + "font-awesome": "components/font-awesome#~4.7.0", "google-caja": "5669", + "jed": "~1.1.1", "jquery": "components/jquery#~2.0", "jquery-typeahead": "~2.0.0", "jquery-ui": "components/jqueryui#~1.10", "marked": "~0.3", "MathJax": "components/MathJax#~2.6", "moment": "~2.8.4", + "preact": "https://unpkg.com/preact@^7.2.0/dist/preact.min.js", + "preact-compat": "https://unpkg.com/preact-compat@^3.14.3/dist/preact-compat.min.js", + "proptypes": "https://unpkg.com/proptypes@^0.14.4/index.js", "requirejs": "~2.1", + "requirejs-text": "~2.0.15", + "requirejs-plugins": "~1.0.3", "text-encoding": "~0.1", "underscore": "components/underscore#~1.8.3", - "xterm.js": "sourcelair/xterm.js#~2.3.2" + "xterm.js": "sourcelair/xterm.js#~2.8.1" } } diff --git a/docs/source/_static/images/cell-tags-toolbar.png b/docs/source/_static/images/cell-tags-toolbar.png index 5058ef872..cab448f9e 100644 Binary files a/docs/source/_static/images/cell-tags-toolbar.png and b/docs/source/_static/images/cell-tags-toolbar.png differ diff --git a/docs/source/_static/images/dashboard-sort.png b/docs/source/_static/images/dashboard-sort.png new file mode 100644 index 000000000..020cc0416 Binary files /dev/null and b/docs/source/_static/images/dashboard-sort.png differ diff --git a/docs/source/_static/images/shortcut-editor.png b/docs/source/_static/images/shortcut-editor.png index 7efd2be9a..efe5fce68 100644 Binary files a/docs/source/_static/images/shortcut-editor.png and b/docs/source/_static/images/shortcut-editor.png differ diff --git a/docs/source/_static/images/table-style-after.png b/docs/source/_static/images/table-style-after.png new file mode 100644 index 000000000..dadbed453 Binary files /dev/null and b/docs/source/_static/images/table-style-after.png differ diff --git a/docs/source/_static/images/table-style-before.png b/docs/source/_static/images/table-style-before.png new file mode 100644 index 000000000..1f4d976b2 Binary files /dev/null and b/docs/source/_static/images/table-style-before.png differ diff --git a/docs/source/_static/images/table-style-change.png b/docs/source/_static/images/table-style-change.png deleted file mode 100644 index 46c139fe4..000000000 Binary files a/docs/source/_static/images/table-style-change.png and /dev/null differ diff --git a/docs/source/_static/images/table-style-change.xcf b/docs/source/_static/images/table-style-change.xcf deleted file mode 100644 index dd34ce265..000000000 Binary files a/docs/source/_static/images/table-style-change.xcf and /dev/null differ diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 67826068a..3ebb18e2b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -12,8 +12,8 @@ For more detailed information, see Use ``pip install notebook --upgrade`` or ``conda upgrade notebook`` to upgrade to the latest release. -.. we push for pip 9+ or it will break for Python 2 users when IPython 6 get out. -Upgrade to version 9+ of pip before upgrading ``notebook`` is strongly recommended. +.. we push for pip 9+ or it will break for Python 2 users when IPython 6 is out. +We strongly recommend that you upgrade to version 9+ of pip before upgrading ``notebook``. .. tip:: @@ -32,9 +32,17 @@ created by the "Big Split" of IPython and Jupyter. We encourage users to start trying JupyterLab in preparation for a future transition. -We have merged more than 200 pull requests since the 4.x series. Some of the +We have merged more than 300 pull requests since 4.0. Some of the major user-facing changes are described here. +File sorting in the dashboard +***************************** + +Files in the dashboard may now be sorted by last modified date or name (:ghpull:`943`): + +.. image:: /_static/images/dashboard-sort.png + :align: center + Cell tags ********* @@ -46,7 +54,8 @@ There is a new cell toolbar for adding *cell tags* (:ghpull:`2048`): Cell tags are a lightweight way to customise the behaviour of tools working with notebooks; we're working on building support for them into tools like `nbconvert `__ and `nbval -`__. +`__. To start using tags, +select ``Tags`` in the ``View > Cell Toolbar`` menu in a notebook. The UI for editing cell tags is basic for now; we hope to improve it in future releases. @@ -54,15 +63,22 @@ releases. Table style *********** -The default styling for tables in the notebook has been updated (:ghpull:`1776`): +The default styling for tables in the notebook has been updated (:ghpull:`1776`). + +Before: -.. image:: /_static/images/table-style-change.png +.. image:: /_static/images/table-style-before.png :align: center + +After: + +.. image:: /_static/images/table-style-after.png + :align: center Customise keyboard shortcuts **************************** -You can now edit keyboard shortcuts for command mode within the UI +You can now edit keyboard shortcuts for *Command Mode* within the UI (:ghpull:`1347`): .. image:: /_static/images/shortcut-editor.png @@ -107,8 +123,23 @@ Other additions - Add more visible *Trusted* and *Untrusted* notifications (:ghpull:`1658`). -- The tab icon in the browser now changes to indicate when the kernel is busy +- The favicon (browser shortcut icon) now changes to indicate when the kernel is busy (:ghpull:`1837`). + +- Header and toolbar visibility is now persisted in nbconfig and across sessions + (:ghpull:`1769`). + +- Load server extensions with ConfigManager so that merge happens recursively, + unlike normal config values, to make it load more consistently with frontend + extensions(:ghpull:`2108`). + +- The notebook server now supports the `bundler API + `__ + from the `jupyter_cms incubator project + `__ (:ghpull:`1579`). + +- The notebook server now provides information about kernel activity in + its kernel resource API (:ghpull:`1827`). Remember that upgrading ``notebook`` only affects the user interface. Upgrading kernels and libraries may also provide new features, diff --git a/docs/source/examples/Notebook/Importing Notebooks.ipynb b/docs/source/examples/Notebook/Importing Notebooks.ipynb index a7cdbcbce..ee42ad526 100644 --- a/docs/source/examples/Notebook/Importing Notebooks.ipynb +++ b/docs/source/examples/Notebook/Importing Notebooks.ipynb @@ -344,7 +344,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "So my notebook has a heading cell and some code cells,\n", + "So my notebook has some code cells,\n", "one of which contains some IPython syntax.\n", "\n", "Let's see what happens when we import it" diff --git a/docs/source/examples/Notebook/Notebook Basics.ipynb b/docs/source/examples/Notebook/Notebook Basics.ipynb index 7ac175c22..aa8419b1d 100644 --- a/docs/source/examples/Notebook/Notebook Basics.ipynb +++ b/docs/source/examples/Notebook/Notebook Basics.ipynb @@ -20,7 +20,7 @@ "source": [ "When you first start the notebook server, your browser will open to the notebook dashboard. The dashboard serves as a home page for the notebook. Its main purpose is to display the notebooks and files in the current directory. For example, here is a screenshot of the dashboard page for the `examples` directory in the Jupyter repository:\n", "\n", - "" + "![Jupyter dashboard showing files tab](images/dashboard_files_tab.png)" ] }, { @@ -31,7 +31,7 @@ "\n", "To create a new notebook, click on the \"New\" button at the top of the list and select a kernel from the dropdown (as seen below). Which kernels are listed depend on what's installed on the server. Some of the kernels in the screenshot below may not exist as an option to you.\n", "\n", - "" + "![Jupyter \"New\" menu](images/dashboard_files_tab_new.png)" ] }, { @@ -42,7 +42,8 @@ "\n", "The notebook list shows green \"Running\" text and a green notebook icon next to running notebooks (as seen below). Notebooks remain running until you explicitly shut them down; closing the notebook's page is not sufficient.\n", "\n", - "" + "\n", + "![Jupyter dashboard showing one notebook with a running kernel](images/dashboard_files_tab_run.png)" ] }, { @@ -51,7 +52,7 @@ "source": [ "To shutdown, delete, duplicate, or rename a notebook check the checkbox next to it and an array of controls will appear at the top of the notebook list (as seen below). You can also use the same operations on directories and files when applicable.\n", "\n", - "" + "![Buttons: Duplicate, rename, shutdown, delete, new, refresh](images/dashboard_files_tab_btns.png)" ] }, { @@ -60,7 +61,7 @@ "source": [ "To see all of your running notebooks along with their directories, click on the \"Running\" tab:\n", "\n", - "\n", + "![Jupyter dashboard running tab](images/dashboard_running_tab.png)\n", "\n", "This view provides a convenient way to track notebooks that you start as you navigate the file system in a long running notebook server." ] @@ -112,7 +113,7 @@ "source": [ "Edit mode is indicated by a green cell border and a prompt showing in the editor area:\n", "\n", - "\n", + "![Jupyter cell with green border](images/edit_mode.png)\n", "\n", "When a cell is in edit mode, you can type into the cell, like a normal text editor." ] @@ -139,7 +140,7 @@ "source": [ "Command mode is indicated by a grey cell border with a blue left margin:\n", "\n", - "\n", + "![Jupyter cell with blue & grey border](images/command_mode.png)\n", "\n", "When you are in command mode, you are able to edit the notebook as a whole, but not type into individual cells. Most importantly, in command mode, the keyboard is mapped to a set of shortcuts that let you perform notebook and cell actions efficiently. For example, if you are in command mode and you press `c`, you will copy the current cell - no modifier is needed." ] @@ -175,7 +176,7 @@ "source": [ "All navigation and actions in the Notebook are available using the mouse through the menubar and toolbar, which are both above the main Notebook area:\n", "\n", - "" + "![Jupyter notebook menus and toolbar](images/menubar_toolbar.png)" ] }, { @@ -193,7 +194,7 @@ "source": [ "The second idea of mouse based navigation is that **cell actions usually apply to the currently selected cell**. Thus if you want to run the code in a cell, you would select it and click the button in the toolbar or the \"Cell:Run\" menu item. Similarly, to copy a cell you would select it and click the button in the toolbar or the \"Edit:Copy\" menu item. With this simple pattern, you should be able to do most everything you need with the mouse.\n", "\n", - "Markdown and heading cells have one other state that can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, you will see a nice formatted representation of the cell's contents. When they are unrendered, you will see the raw text source of the cell. To render the selected cell with the mouse, click the button in the toolbar or the \"Cell:Run\" menu item. To unrender the selected cell, double click on the cell." + "Markdown cells have one other state that can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, you will see a nice formatted representation of the cell's contents. When they are unrendered, you will see the raw text source of the cell. To render the selected cell with the mouse, click the button in the toolbar or the \"Cell:Run\" menu item. To unrender the selected cell, double click on the cell." ] }, { @@ -245,9 +246,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb b/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb index a5edb80e8..af9b187fc 100644 --- a/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb +++ b/docs/source/examples/Notebook/What is the Jupyter Notebook.ipynb @@ -79,7 +79,6 @@ "* See the results of computations with **rich media representations**, such as HTML, LaTeX, PNG, SVG, PDF, etc.\n", "* Create and use **interactive JavaScript widgets**, which bind interactive user interface controls and visualizations to reactive kernel side computations.\n", "* Author **narrative text** using the [Markdown](https://daringfireball.net/projects/markdown/) markup language.\n", - "* Build **hierarchical documents** that are organized into sections with different levels of headings.\n", "* Include mathematical equations using **LaTeX syntax in Markdown**, which are rendered in-browser by [MathJax](http://www.mathjax.org/)." ] }, @@ -111,7 +110,7 @@ "\n", "The default kernel runs Python code. The notebook provides a simple way for users to pick which of these kernels is used for a given notebook. \n", "\n", - "Each of these kernels communicate with the notebook web application and web browser using a JSON over ZeroMQ/WebSockets message protocol that is described [here](http://ipython.org/ipython-doc/dev/development/messaging.html). Most users don't need to know about these details, but it helps to understand that \"kernels run code.\"" + "Each of these kernels communicate with the notebook web application and web browser using a JSON over ZeroMQ/WebSockets message protocol that is described [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging). Most users don't need to know about these details, but it helps to understand that \"kernels run code.\"" ] }, { diff --git a/docs/source/examples/Notebook/Working With Markdown Cells.ipynb b/docs/source/examples/Notebook/Working With Markdown Cells.ipynb index 1e03b8319..b7ccc62e1 100644 --- a/docs/source/examples/Notebook/Working With Markdown Cells.ipynb +++ b/docs/source/examples/Notebook/Working With Markdown Cells.ipynb @@ -189,17 +189,13 @@ "source": [ "The Notebook webapp supports Github flavored markdown meaning that you can use triple backticks for code blocks:\n", "\n", - "
\n",
     "    ```python\n",
     "    print \"Hello World\"\n",
     "    ```\n",
-    "    
\n", "\n", - "
\n",
     "    ```javascript\n",
     "    console.log(\"Hello World\")\n",
     "    ```\n",
-    "    
\n", "\n", "Gives:\n", "\n", @@ -213,16 +209,10 @@ "\n", "And a table like this: \n", "\n", - "
\n",
-    "    ```\n",
-    "\n",
     "    | This | is   |\n",
     "    |------|------|\n",
     "    |   a  | table| \n",
     "\n",
-    "    ```\n",
-    "    
\n", - "\n", "A nice HTML Table:\n", "\n", "| This | is |\n", @@ -278,7 +268,7 @@ "\n", " \n", "\n", - "\n", + "\n", "\n", "and a video with the HTML5 video tag:\n", "\n", @@ -328,7 +318,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/docs/source/extending/handlers.rst b/docs/source/extending/handlers.rst index f8a7dc613..90bed96a9 100644 --- a/docs/source/extending/handlers.rst +++ b/docs/source/extending/handlers.rst @@ -31,33 +31,33 @@ when the extension is loaded. To get the notebook server to load your custom extension, you'll need to add it to the list of extensions to be loaded. You can do this using the -config system. ``NotebookApp.server_extensions`` is a config variable -which is an array of strings, each a Python module to be imported. +config system. ``NotebookApp.nbserver_extensions`` is a config variable +which is a dictionary of strings, each a Python module to be imported, mapping +to ``True`` to enable or ``False`` to disable each extension. Because this variable is notebook config, you can set it two different ways, using config files or via the command line. For example, to get your extension to load via the command line add a -double dash before the variable name, and put the Python array in +double dash before the variable name, and put the Python dictionary in double quotes. If your package is "mypackage" and module is "mymodule", this would look like -``jupyter notebook --NotebookApp.server_extensions="['mypackage.mymodule']"`` +``jupyter notebook --NotebookApp.nbserver_extensions="{'mypackage.mymodule':True}"`` . Basically the string should be Python importable. Alternatively, you can have your extension loaded regardless of the command line args by setting the variable in the Jupyter config file. The default location of the Jupyter config file is -``~/.jupyter/profile_default/jupyter_notebook_config.py``. Then, inside +``~/.jupyter/jupyter_notebook_config.py`` (see :doc:`/config_overview`). Inside the config file, you can use Python to set the variable. For example, -the following config does the same as the previous command line example -[1]. +the following config does the same as the previous command line example. .. code:: python c = get_config() - c.NotebookApp.server_extensions = [ - 'mypackage.mymodule' - ] + c.NotebookApp.nbserver_extensions = { + 'mypackage.mymodule': True, + } Before continuing, it's a good idea to verify that your extension is being loaded. Use a print statement to print something unique. Launch diff --git a/docs/source/frontend_config.rst b/docs/source/frontend_config.rst index 6c4280ad5..be3699ffd 100644 --- a/docs/source/frontend_config.rst +++ b/docs/source/frontend_config.rst @@ -61,7 +61,7 @@ four spaces. Enter the following code snippet in your JavaScript console:: var config = cell.config; var patch = { CodeCell:{ - cm_config:{indentUnit: null} # only change here. + cm_config:{indentUnit: null} // only change here. } } config.update(patch) diff --git a/docs/source/notebook.rst b/docs/source/notebook.rst index f50721018..9bbb7bdfc 100644 --- a/docs/source/notebook.rst +++ b/docs/source/notebook.rst @@ -208,15 +208,14 @@ operations within the notebook, by clicking on an icon. Structure of a notebook document -------------------------------- -The notebook consists of a sequence of cells. A cell is a multiline -text input field, and its contents can be executed by using -:kbd:`Shift-Enter`, or by clicking either the "Play" button the toolbar, or -`Cell | Run` in the menu bar. The execution behavior of a cell is determined -the cell's type. There are four types of cells: **code cells**, **markdown -cells**, **raw cells** and **heading cells**. Every cell starts off -being a **code cell**, but its type can be changed by using a drop-down on the -toolbar (which will be "Code", initially), or via :ref:`keyboard shortcuts -`. +The notebook consists of a sequence of cells. A cell is a multiline text input +field, and its contents can be executed by using :kbd:`Shift-Enter`, or by +clicking either the "Play" button the toolbar, or `Cell | Run` in the menu bar. +The execution behavior of a cell is determined the cell's type. There are four +types of cells: **code cells**, **markdown cells**, and **raw cells**. Every +cell starts off being a **code cell**, but its type can be changed by using a +drop-down on the toolbar (which will be "Code", initially), or via +:ref:`keyboard shortcuts `. For more information on the different things you can do in a notebook, see the `collection of examples @@ -250,6 +249,11 @@ called *Markdown cells*. The Markdown language provides a simple way to perform this text markup, that is, to specify which parts of the text should be emphasized (italics), bold, form lists, etc. +If you want to provide structure for your document, you can use markdown +headings. Markdown headings consist of 1 to 6 hash # signs ``#`` followed by a +space and the title of your section. The markdown heading will be converted +to a clickable link for a section of the notebook. It is also used as a hint +when exporting to other document formats, like PDF. When a Markdown cell is executed, the Markdown code is converted into the corresponding formatted rich text. Markdown allows arbitrary HTML code for @@ -286,17 +290,6 @@ destination format unmodified. For example, this allows you to type full LaTeX into a raw cell, which will only be rendered by LaTeX after conversion by nbconvert. -Heading cells -~~~~~~~~~~~~~ - -If you want to provide structure for your document, you can use markdown -headings. Markdown headings consist of 1 to 6 hash # signs ``#`` followed by a -space and the title of your section. The markdown heading will be converted -to a clickable link for a section of the notebook. It is also used as a hint -when exporting to other document formats, like PDF. -We recommend using only one markdown header in a cell and limit the cell's -content to the header text. For flexibility of text format conversion, we -suggest placing additional text in the next notebook cell. Basic workflow -------------- diff --git a/docs/source/public_server.rst b/docs/source/public_server.rst index efe5a86e3..fd079b5ab 100644 --- a/docs/source/public_server.rst +++ b/docs/source/public_server.rst @@ -289,7 +289,7 @@ with the following configuration setting in c.NotebookApp.tornado_settings = { 'headers': { - 'Content-Security-Policy': "frame-ancestors 'https://mywebsite.example.com' 'self' " + 'Content-Security-Policy': "frame-ancestors https://mywebsite.example.com 'self' " } } diff --git a/docs/source/security.rst b/docs/source/security.rst index aaa8dfce3..f90f9d6da 100644 --- a/docs/source/security.rst +++ b/docs/source/security.rst @@ -12,7 +12,7 @@ For this reason, notebook 4.3 introduces token-based authentication that is **on If you enable a password for your notebook server, token authentication is not enabled by default, - and the behavior of the notebook server is unchanged from from versions earlier than 4.3. + and the behavior of the notebook server is unchanged from versions earlier than 4.3. When token authentication is enabled, the notebook uses a token to authenticate requests. This token can be provided to login to the notebook server in three ways: @@ -31,7 +31,8 @@ When you start a notebook server with token authentication enabled (default), a token is generated to use for authentication. This token is logged to the terminal, so that you can copy/paste the URL into your browser:: - [I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01 + [I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at: + http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01 If the notebook server is going to open your browser automatically diff --git a/notebook/_sysinfo.py b/notebook/_sysinfo.py index ea237bbd2..4e6a36626 100644 --- a/notebook/_sysinfo.py +++ b/notebook/_sysinfo.py @@ -46,11 +46,15 @@ def pkg_commit_hash(pkg_path): while cur_path != par_path: cur_path = par_path if p.exists(p.join(cur_path, '.git')): - proc = subprocess.Popen('git rev-parse --short HEAD', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=pkg_path, shell=True) - repo_commit, _ = proc.communicate() + try: + proc = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=pkg_path) + repo_commit, _ = proc.communicate() + except OSError: + repo_commit = None + if repo_commit: return 'repository', repo_commit.strip().decode('ascii') else: diff --git a/notebook/_version.py b/notebook/_version.py index cb9d8670a..567a534a0 100644 --- a/notebook/_version.py +++ b/notebook/_version.py @@ -9,5 +9,5 @@ store the current version info of the notebook. # Next beta/alpha/rc release: The version number for beta is X.Y.ZbN **without dots**. -version_info = (5, 0, 0, '.dev') +version_info = (5, 1, 0, '.dev') __version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:]) diff --git a/notebook/allow76.py b/notebook/allow76.py deleted file mode 100644 index 67e87e8d9..000000000 --- a/notebook/allow76.py +++ /dev/null @@ -1,311 +0,0 @@ -"""WebsocketProtocol76 from tornado 3.2.2 for tornado >= 4.0 - -The contents of this file are Copyright (c) Tornado -Used under the Apache 2.0 license -""" - - -from __future__ import absolute_import, division, print_function, with_statement -# Author: Jacob Kristhammar, 2010 - -import functools -import hashlib -import struct -import time -import tornado.escape -import tornado.web - -from tornado.log import gen_log, app_log -from tornado.util import bytes_type, unicode_type - -from tornado.websocket import WebSocketHandler, WebSocketProtocol13 - -class AllowDraftWebSocketHandler(WebSocketHandler): - """Restore Draft76 support for tornado 4 - - Remove when we can run tests without phantomjs + qt4 - """ - - # get is unmodified except between the BEGIN/END PATCH lines - @tornado.web.asynchronous - def get(self, *args, **kwargs): - self.open_args = args - self.open_kwargs = kwargs - - # Upgrade header should be present and should be equal to WebSocket - if self.request.headers.get("Upgrade", "").lower() != 'websocket': - self.set_status(400) - self.finish("Can \"Upgrade\" only to \"WebSocket\".") - return - - # Connection header should be upgrade. Some proxy servers/load balancers - # might mess with it. - headers = self.request.headers - connection = map(lambda s: s.strip().lower(), headers.get("Connection", "").split(",")) - if 'upgrade' not in connection: - self.set_status(400) - self.finish("\"Connection\" must be \"Upgrade\".") - return - - # Handle WebSocket Origin naming convention differences - # The difference between version 8 and 13 is that in 8 the - # client sends a "Sec-Websocket-Origin" header and in 13 it's - # simply "Origin". - if "Origin" in self.request.headers: - origin = self.request.headers.get("Origin") - else: - origin = self.request.headers.get("Sec-Websocket-Origin", None) - - - # If there was an origin header, check to make sure it matches - # according to check_origin. When the origin is None, we assume it - # did not come from a browser and that it can be passed on. - if origin is not None and not self.check_origin(origin): - self.set_status(403) - self.finish("Cross origin websockets not allowed") - return - - self.stream = self.request.connection.detach() - self.stream.set_close_callback(self.on_connection_close) - - if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"): - self.ws_connection = WebSocketProtocol13(self) - self.ws_connection.accept_connection() - #--------------- BEGIN PATCH ---------------- - elif (self.allow_draft76() and - "Sec-WebSocket-Version" not in self.request.headers): - self.ws_connection = WebSocketProtocol76(self) - self.ws_connection.accept_connection() - #--------------- END PATCH ---------------- - else: - if not self.stream.closed(): - self.stream.write(tornado.escape.utf8( - "HTTP/1.1 426 Upgrade Required\r\n" - "Sec-WebSocket-Version: 8\r\n\r\n")) - self.stream.close() - - # 3.2 methods removed in 4.0: - def allow_draft76(self): - """Using this class allows draft76 connections by default""" - return True - - def get_websocket_scheme(self): - """Return the url scheme used for this request, either "ws" or "wss". - This is normally decided by HTTPServer, but applications - may wish to override this if they are using an SSL proxy - that does not provide the X-Scheme header as understood - by HTTPServer. - Note that this is only used by the draft76 protocol. - """ - return "wss" if self.request.protocol == "https" else "ws" - - - -# No modifications from tornado-3.2.2 below this line - -class WebSocketProtocol(object): - """Base class for WebSocket protocol versions. - """ - def __init__(self, handler): - self.handler = handler - self.request = handler.request - self.stream = handler.stream - self.client_terminated = False - self.server_terminated = False - - def async_callback(self, callback, *args, **kwargs): - """Wrap callbacks with this if they are used on asynchronous requests. - - Catches exceptions properly and closes this WebSocket if an exception - is uncaught. - """ - if args or kwargs: - callback = functools.partial(callback, *args, **kwargs) - - def wrapper(*args, **kwargs): - try: - return callback(*args, **kwargs) - except Exception: - app_log.error("Uncaught exception in %s", - self.request.path, exc_info=True) - self._abort() - return wrapper - - def on_connection_close(self): - self._abort() - - def _abort(self): - """Instantly aborts the WebSocket connection by closing the socket""" - self.client_terminated = True - self.server_terminated = True - self.stream.close() # forcibly tear down the connection - self.close() # let the subclass cleanup - - -class WebSocketProtocol76(WebSocketProtocol): - """Implementation of the WebSockets protocol, version hixie-76. - - This class provides basic functionality to process WebSockets requests as - specified in - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - """ - def __init__(self, handler): - WebSocketProtocol.__init__(self, handler) - self.challenge = None - self._waiting = None - - def accept_connection(self): - try: - self._handle_websocket_headers() - except ValueError: - gen_log.debug("Malformed WebSocket request received") - self._abort() - return - - scheme = self.handler.get_websocket_scheme() - - # draft76 only allows a single subprotocol - subprotocol_header = '' - subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None) - if subprotocol: - selected = self.handler.select_subprotocol([subprotocol]) - if selected: - assert selected == subprotocol - subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected - - # Write the initial headers before attempting to read the challenge. - # This is necessary when using proxies (such as HAProxy), which - # need to see the Upgrade headers before passing through the - # non-HTTP traffic that follows. - self.stream.write(tornado.escape.utf8( - "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" - "Upgrade: WebSocket\r\n" - "Connection: Upgrade\r\n" - "Server: TornadoServer/%(version)s\r\n" - "Sec-WebSocket-Origin: %(origin)s\r\n" - "Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n" - "%(subprotocol)s" - "\r\n" % (dict( - version=tornado.version, - origin=self.request.headers["Origin"], - scheme=scheme, - host=self.request.host, - uri=self.request.uri, - subprotocol=subprotocol_header)))) - self.stream.read_bytes(8, self._handle_challenge) - - def challenge_response(self, challenge): - """Generates the challenge response that's needed in the handshake - - The challenge parameter should be the raw bytes as sent from the - client. - """ - key_1 = self.request.headers.get("Sec-Websocket-Key1") - key_2 = self.request.headers.get("Sec-Websocket-Key2") - try: - part_1 = self._calculate_part(key_1) - part_2 = self._calculate_part(key_2) - except ValueError: - raise ValueError("Invalid Keys/Challenge") - return self._generate_challenge_response(part_1, part_2, challenge) - - def _handle_challenge(self, challenge): - try: - challenge_response = self.challenge_response(challenge) - except ValueError: - gen_log.debug("Malformed key data in WebSocket request") - self._abort() - return - self._write_response(challenge_response) - - def _write_response(self, challenge): - self.stream.write(challenge) - self.async_callback(self.handler.open)(*self.handler.open_args, **self.handler.open_kwargs) - self._receive_message() - - def _handle_websocket_headers(self): - """Verifies all invariant- and required headers - - If a header is missing or have an incorrect value ValueError will be - raised - """ - fields = ("Origin", "Host", "Sec-Websocket-Key1", - "Sec-Websocket-Key2") - if not all(map(lambda f: self.request.headers.get(f), fields)): - raise ValueError("Missing/Invalid WebSocket headers") - - def _calculate_part(self, key): - """Processes the key headers and calculates their key value. - - Raises ValueError when feed invalid key.""" - # pyflakes complains about variable reuse if both of these lines use 'c' - number = int(''.join(c for c in key if c.isdigit())) - spaces = len([c2 for c2 in key if c2.isspace()]) - try: - key_number = number // spaces - except (ValueError, ZeroDivisionError): - raise ValueError - return struct.pack(">I", key_number) - - def _generate_challenge_response(self, part_1, part_2, part_3): - m = hashlib.md5() - m.update(part_1) - m.update(part_2) - m.update(part_3) - return m.digest() - - def _receive_message(self): - self.stream.read_bytes(1, self._on_frame_type) - - def _on_frame_type(self, byte): - frame_type = ord(byte) - if frame_type == 0x00: - self.stream.read_until(b"\xff", self._on_end_delimiter) - elif frame_type == 0xff: - self.stream.read_bytes(1, self._on_length_indicator) - else: - self._abort() - - def _on_end_delimiter(self, frame): - if not self.client_terminated: - self.async_callback(self.handler.on_message)( - frame[:-1].decode("utf-8", "replace")) - if not self.client_terminated: - self._receive_message() - - def _on_length_indicator(self, byte): - if ord(byte) != 0x00: - self._abort() - return - self.client_terminated = True - self.close() - - def write_message(self, message, binary=False): - """Sends the given message to the client of this Web Socket.""" - if binary: - raise ValueError( - "Binary messages not supported by this version of websockets") - if isinstance(message, unicode_type): - message = message.encode("utf-8") - assert isinstance(message, bytes_type) - self.stream.write(b"\x00" + message + b"\xff") - - def write_ping(self, data): - """Send ping frame.""" - raise ValueError("Ping messages not supported by this version of websockets") - - def close(self): - """Closes the WebSocket connection.""" - if not self.server_terminated: - if not self.stream.closed(): - self.stream.write("\xff\x00") - self.server_terminated = True - if self.client_terminated: - if self._waiting is not None: - self.stream.io_loop.remove_timeout(self._waiting) - self._waiting = None - self.stream.close() - elif self._waiting is None: - self._waiting = self.stream.io_loop.add_timeout( - time.time() + 5, self._abort) - diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 2c48d5cc1..76651e9e0 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -5,6 +5,7 @@ import functools import json +import mimetypes import os import re import sys @@ -39,7 +40,12 @@ from notebook.services.security import csp_report_uri #----------------------------------------------------------------------------- non_alphanum = re.compile(r'[^A-Za-z0-9]') -sys_info = json.dumps(get_sys_info()) +_sys_info_cache = None +def json_sys_info(): + global _sys_info_cache + if _sys_info_cache is None: + _sys_info_cache = json.dumps(get_sys_info()) + return _sys_info_cache def log(): if Application.initialized(): @@ -56,20 +62,24 @@ class AuthenticatedHandler(web.RequestHandler): Can be overridden by defining Content-Security-Policy in settings['headers'] """ + if 'Content-Security-Policy' in self.settings.get('headers', {}): + # user-specified, don't override + return self.settings['headers']['Content-Security-Policy'] + return '; '.join([ "frame-ancestors 'self'", # Make sure the report-uri is relative to the base_url - "report-uri " + url_path_join(self.base_url, csp_report_uri), + "report-uri " + self.settings.get('csp_report_uri', url_path_join(self.base_url, csp_report_uri)), ]) def set_default_headers(self): - headers = self.settings.get('headers', {}) + headers = {} + headers.update(self.settings.get('headers', {})) - if "Content-Security-Policy" not in headers: - headers["Content-Security-Policy"] = self.content_security_policy + headers["Content-Security-Policy"] = self.content_security_policy # Allow for overriding headers - for header_name,value in headers.items() : + for header_name, value in headers.items(): try: self.set_header(header_name, value) except Exception as e: @@ -357,7 +367,7 @@ class IPythonHandler(AuthenticatedHandler): login_available=self.login_available, token_available=bool(self.token or self.one_time_token), static_url=self.static_url, - sys_info=sys_info, + sys_info=json_sys_info(), contents_js_source=self.contents_js_source, version_hash=self.version_hash, ignore_minified_js=self.ignore_minified_js, @@ -466,13 +476,27 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler): @web.authenticated def get(self, path): - if os.path.splitext(path)[1] == '.ipynb': + if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False): name = path.rsplit('/', 1)[-1] - self.set_header('Content-Type', 'application/json') self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name)) - + return web.StaticFileHandler.get(self, path) + def get_content_type(self): + path = self.absolute_path.strip('/') + if '/' in path: + _, name = path.rsplit('/', 1) + else: + name = path + if name.endswith('.ipynb'): + return 'application/x-ipynb+json' + else: + cur_mime = mimetypes.guess_type(name)[0] + if cur_mime == 'text/plain': + return 'text/plain; charset=UTF-8' + else: + return super(AuthenticatedFileHandler, self).get_content_type() + def set_headers(self): super(AuthenticatedFileHandler, self).set_headers() # disable browser caching, rely on 304 replies for savings @@ -684,5 +708,6 @@ path_regex = r"(?P(?:(?:/[^/]+)+|/?))" default_handlers = [ (r".*/", TrailingSlashHandler), - (r"api", APIVersionHandler) + (r"api", APIVersionHandler), + (r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler), ] diff --git a/notebook/base/zmqhandlers.py b/notebook/base/zmqhandlers.py index 66e31eb18..c922d102f 100644 --- a/notebook/base/zmqhandlers.py +++ b/notebook/base/zmqhandlers.py @@ -87,14 +87,6 @@ def deserialize_binary_message(bmsg): # ping interval for keeping websockets alive (30 seconds) WS_PING_INTERVAL = 30000 -if os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False): - warnings.warn("""Allowing draft76 websocket connections! - This should only be done for testing with phantomjs!""") - from notebook import allow76 - WebSocketHandler = allow76.AllowDraftWebSocketHandler - # draft 76 doesn't support ping - WS_PING_INTERVAL = 0 - class WebSocketMixin(object): """Mixin for common websocket options""" @@ -296,5 +288,4 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): self.session = Session(config=self.config) def get_compression_options(self): - # use deflate compress websocket - return {} + return self.settings.get('websocket_compression_options', None) diff --git a/notebook/files/handlers.py b/notebook/files/handlers.py index c54151125..4eaeb0395 100644 --- a/notebook/files/handlers.py +++ b/notebook/files/handlers.py @@ -26,6 +26,13 @@ class FilesHandler(IPythonHandler): @web.authenticated def get(self, path, include_body=True): cm = self.contents_manager + + if cm.files_handler_class: + return cm.files_handler_class(self.application, self.request, path=cm.root_dir)._execute( + [t(self.request) for t in self.application.transforms], + path + ) + if cm.is_hidden(path): self.log.info("Refusing to serve hidden file, via 404 Error") raise web.HTTPError(404) @@ -46,13 +53,15 @@ class FilesHandler(IPythonHandler): self.set_header('Content-Type', 'application/x-ipynb+json') else: cur_mime = mimetypes.guess_type(name)[0] - if cur_mime is not None: + if cur_mime == 'text/plain': + self.set_header('Content-Type', 'text/plain; charset=UTF-8') + elif cur_mime is not None: self.set_header('Content-Type', cur_mime) else: if model['format'] == 'base64': self.set_header('Content-Type', 'application/octet-stream') else: - self.set_header('Content-Type', 'text/plain') + self.set_header('Content-Type', 'text/plain; charset=UTF-8') if include_body: if model['format'] == 'base64': diff --git a/notebook/i18n/README.md b/notebook/i18n/README.md new file mode 100644 index 000000000..e43914257 --- /dev/null +++ b/notebook/i18n/README.md @@ -0,0 +1,122 @@ +# Implementation Notes for Internationalization of Jupyter Notebook + +This is a prototype implementation of i18n features for Jupyter notebook, and should not +yet be considered ready for production use. I have tried to focus on the public user +interfaces in the notebook for the first cut, while leaving much of the console messages +behind, as their usefulness in a translated environment is questionable at best. + +### Using a prototype translated version + +In order to use this preliminary version, you need to do things after installing the +notebook as normal: + +1. Set the LANG environment variable in your shell to "xx_XX" or just "xx". +where "xx" is the language code you're wanting to run in. If you're +running on Windows, I've found the easiest way to do this is to use Windows PowerShell, +and run the command: + +`${Env:LANG} = "xx_XX"` + +2. Set the preferred language for web pages in your browser to YourLanguage (xx). At the moment, +it has to be first in the list. + +3. Run the `jupyter notebook` command to start the notebook. + +### Message extraction: + +I have split out the translatable material for the notebook into 3 POT, as follows: + +notebook/i18n/notebook.pot - Console and startup messages, basically anything that is + produced by Python code. + +notebook/i18n/nbui.pot - User interface strings, as extracted from the Jinja2 templates + in notebook/templates/*.html + +noteook/i18n/nbjs.pot - JavaScript strings and dialogs, which contain much of the visible + user interface for Jupyter notebook. + +To extract the messages from the source code whenever new material is added, use the +`pybabel` command to extract messages from the source code as follows: +( assuming you are in the base directory for Jupyter notebook ) + +`pybabel extract -F notebook/i18n/babel_notebook.cfg -o notebook/i18n/notebook.pot --no-wrap --project Jupyter .` +`pybabel extract -F notebook/i18n/babel_nbui.cfg -o notebook/i18n/nbui.pot --no-wrap --project Jupyter .` +`pybabel extract -F notebook/i18n/babel_nbjs.cfg -o notebook/i18n/nbjs.pot --no-wrap --project Jupyter .` + +(Note: there is a '.' at the end of these commands, and it has to be there...) + +After this is complete you have 3 POT files that you can give to a translator for your favorite language. +Babel's documentation has instructions on how to integrate this into your setup.py so that eventually +we can just do: + +`setup.py extract_messages` + +I hope to get this working at some point in the near future. + +### Post translation procedures + +After the source material has been translated, you should have 3 PO files with the same base names +as the POT files above. Put them in `notebook/i18n/${LANG}/LC_MESSAGES`, where ${LANG} is the language +code for your desired language ( i.e. German = "de", Japanese = "ja", etc. ). The first 2 files then +need to be converted from PO to MO format for use at runtime. There are many different ways to do +this, but pybabel has an option to do this as follows: + +`pybabel compile -D notebook -f -l ${LANG} -i notebook/i18n/${LANG}/LC_MESSAGES/notebook.po -o notebook/i18n/${LANG}/notebook.mo` + +`pybabel compile -D nbui -f -l ${LANG} -i notebook/i18n/${LANG}/LC_MESSAGES/nbui.po -o notebook/i18n/${LANG}/nbui.mo` + +The nbjs.po needs to be converted to JSON for use within the JavaScript code. I'm using po2json for this, as follows: + +`po2json -p -F -f jed1.x -d nbjs notebook/i18n/${LANG}/LC_MESSAGES/nbjs.po notebook/i18n/${LANG}/LC_MESSAGES/nbjs.json` + +The conversions from PO to MO probably can and should be done during setup.py. + +When new languages get added, their language codes should be added to notebook/i18n/nbjs.json +under the "supported_languages" element. + +### Tips for Jupyter developers + +The biggest "mistake" I found while doing i18n enablement was the habit of constructing UI messages +from English "piece parts". For example, code like: + + +`var msg = "Enter a new " + type + "name:"` + +where "type" is either "file", "directory", or "notebook".... + +is problematic when doing translations, because the surrounding text may need to vary +depending on the inserted word. In this case, you need to switch it and use complete phrases, +as follows: + +```javascript +var rename_msg = function (type) { + switch(type) { + case 'file': return _("Enter a new file name:"); + case 'directory': return _("Enter a new directory name:"); + case 'notebook': return _("Enter a new notebook name:"); + default: return _("Enter a new name:"); + } +} +``` + +Also you need to remember that adding an "s" or "es" to an English word to +create the plural form doesn't translate well. Some languages have as many as 5 or 6 different +plural forms for differing numbers, so using an API such as ngettext() is necessary in order +to handle these cases properly. + +### Known issues + +1. Right now there are two different places where the desired language is set. At startup time, the Jupyter console's messages pay attention to the setting of the ${LANG} environment variable +as set in the shell at startup time. Unfortunately, this is also the time where the Jinja2 +environment is set up, which means that the template stuff will always come from this setting. +We really want to be paying attention to the browser's settings for the stuff that happens in the +browser, so we need to be able to retrieve this information after the browser is started and somehow +communicate this back to Jinja2. So far, I haven't yet figured out how to do this, which means that if the ${LANG} at startup doesn't match the browser's settings, you could potentially get a mix +of languages in the UI ( never a good thing ). + +2. We will need to decide if console messages should be translatable, and enable them if desired. +3. The keyboard shorcut editor was implemented after the i18n work was completed, so that portion +does not have translation support at this time. + +Any questions or comments please let me know @JCEmmons on github (emmo@us.ibm.com) + diff --git a/notebook/i18n/babel_nbjs.cfg b/notebook/i18n/babel_nbjs.cfg new file mode 100644 index 000000000..492f24773 --- /dev/null +++ b/notebook/i18n/babel_nbjs.cfg @@ -0,0 +1,11 @@ +[javascript: notebook/static/base/js/*.js] +extract_messages = $._, i18n.msg._ + +[javascript: notebook/static/notebook/js/*.js] +extract_messages = $._, i18n.msg._ + +[javascript: notebook/static/notebook/js/celltoolbarpresets/*.js] +extract_messages = $._, i18n.msg._ + +[javascript: notebook/static/tree/js/*.js] +extract_messages = $._, i18n.msg._ diff --git a/notebook/i18n/babel_nbui.cfg b/notebook/i18n/babel_nbui.cfg new file mode 100644 index 000000000..271554a8a --- /dev/null +++ b/notebook/i18n/babel_nbui.cfg @@ -0,0 +1,4 @@ +[jinja2: notebook/templates/**.html] + encoding = utf-8 +[extractors] + jinja2 = jinja2.ext:babel_extract diff --git a/notebook/i18n/babel_notebook.cfg b/notebook/i18n/babel_notebook.cfg new file mode 100644 index 000000000..d4e3cf9b7 --- /dev/null +++ b/notebook/i18n/babel_notebook.cfg @@ -0,0 +1,2 @@ +[python: notebook/*.py] +[python: notebook/services/contents/*.py] diff --git a/notebook/i18n/nbjs.json b/notebook/i18n/nbjs.json new file mode 100644 index 000000000..d12cecdca --- /dev/null +++ b/notebook/i18n/nbjs.json @@ -0,0 +1,12 @@ +{ + "domain": "nbjs", + "supported_languages": [ + ], + "locale_data": { + "nbjs": { + "": { + "domain": "nbjs" + } + } + } +} diff --git a/notebook/i18n/nbjs.pot b/notebook/i18n/nbjs.pot new file mode 100644 index 000000000..babef4190 --- /dev/null +++ b/notebook/i18n/nbjs.pot @@ -0,0 +1,1927 @@ +# Translations template for Jupyter. +# Copyright (C) 2017 ORGANIZATION +# This file is distributed under the same license as the Jupyter project. +# FIRST AUTHOR , 2017. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Jupyter VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2017-06-27 14:04-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.3.4\n" + +#: notebook/static/base/js/dialog.js:161 +msgid "Manually edit the JSON below to manipulate the metadata for this cell." +msgstr "" + +#: notebook/static/base/js/dialog.js:163 +msgid "Manually edit the JSON below to manipulate the metadata for this notebook." +msgstr "" + +#: notebook/static/base/js/dialog.js:165 +msgid " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others." +msgstr "" + +#: notebook/static/base/js/dialog.js:180 +msgid "Edit the metadata" +msgstr "" + +#: notebook/static/base/js/dialog.js:202 +msgid "Edit Notebook Metadata" +msgstr "" + +#: notebook/static/base/js/dialog.js:204 +msgid "Edit Cell Metadata" +msgstr "" + +#: notebook/static/base/js/dialog.js:208 +#: notebook/static/notebook/js/notebook.js:475 +#: notebook/static/notebook/js/savewidget.js:71 +#: notebook/static/tree/js/notebooklist.js:859 +#: notebook/static/tree/js/notebooklist.js:1418 +msgid "Cancel" +msgstr "" + +#: notebook/static/base/js/dialog.js:208 +msgid "Edit" +msgstr "" + +#: notebook/static/base/js/dialog.js:208 +#: notebook/static/notebook/js/kernelselector.js:278 +#: notebook/static/notebook/js/mathjaxutils.js:42 +#: notebook/static/notebook/js/notebook.js:469 +#: notebook/static/notebook/js/notificationarea.js:187 +#: notebook/static/notebook/js/savewidget.js:71 +#: notebook/static/tree/js/newnotebook.js:97 +#: notebook/static/tree/js/notebooklist.js:859 +msgid "OK" +msgstr "" + +#: notebook/static/base/js/dialog.js:208 +msgid "Apply" +msgstr "" + +#: notebook/static/base/js/dialog.js:225 +msgid "WARNING: Could not save invalid JSON." +msgstr "" + +#: notebook/static/base/js/dialog.js:247 +msgid "There are no attachments for this cell." +msgstr "" + +#: notebook/static/base/js/dialog.js:250 +msgid "Current cell attachments" +msgstr "" + +#: notebook/static/base/js/dialog.js:259 +#: notebook/static/notebook/js/celltoolbarpresets/attachments.js:46 +msgid "Attachments" +msgstr "" + +#: notebook/static/base/js/dialog.js:283 +msgid "Restore" +msgstr "" + +#: notebook/static/base/js/dialog.js:293 +#: notebook/static/tree/js/notebooklist.js:1018 +msgid "Delete" +msgstr "" + +#: notebook/static/base/js/dialog.js:342 notebook/static/base/js/dialog.js:386 +msgid "Edit attachments" +msgstr "" + +#: notebook/static/base/js/dialog.js:348 +msgid "Edit Notebook Attachments" +msgstr "" + +#: notebook/static/base/js/dialog.js:350 +msgid "Edit Cell Attachments" +msgstr "" + +#: notebook/static/base/js/dialog.js:373 +msgid "Select a file to insert." +msgstr "" + +#: notebook/static/base/js/dialog.js:399 +msgid "Select a file" +msgstr "" + +#: notebook/static/notebook/js/about.js:14 +msgid "You are using Jupyter notebook." +msgstr "" + +#: notebook/static/notebook/js/about.js:16 +msgid "The version of the notebook server is: " +msgstr "" + +#: notebook/static/notebook/js/about.js:22 +msgid "The server is running on this version of Python:" +msgstr "" + +#: notebook/static/notebook/js/about.js:25 +msgid "Waiting for kernel to be available..." +msgstr "" + +#: notebook/static/notebook/js/about.js:27 +msgid "Server Information:" +msgstr "" + +#: notebook/static/notebook/js/about.js:29 +msgid "Current Kernel Information:" +msgstr "" + +#: notebook/static/notebook/js/about.js:32 +msgid "Could not access sys_info variable for version information." +msgstr "" + +#: notebook/static/notebook/js/about.js:34 +msgid "Cannot find sys_info!" +msgstr "" + +#: notebook/static/notebook/js/about.js:38 +msgid "About Jupyter Notebook" +msgstr "" + +#: notebook/static/notebook/js/about.js:47 +msgid "unable to contact kernel" +msgstr "" + +#: notebook/static/notebook/js/actions.js:69 +msgid "toggle rtl layout" +msgstr "" + +#: notebook/static/notebook/js/actions.js:70 +msgid "Toggle the screen directionality between left-to-right and right-to-left" +msgstr "" + +#: notebook/static/notebook/js/actions.js:76 +msgid "edit command mode keyboard shortcuts" +msgstr "" + +#: notebook/static/notebook/js/actions.js:77 +msgid "Open a dialog to edit the command mode keyboard shortcuts" +msgstr "" + +#: notebook/static/notebook/js/actions.js:97 +msgid "restart kernel" +msgstr "" + +#: notebook/static/notebook/js/actions.js:98 +msgid "restart the kernel (no confirmation dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:106 +msgid "confirm restart kernel" +msgstr "" + +#: notebook/static/notebook/js/actions.js:107 +msgid "restart the kernel (with dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:113 +msgid "restart kernel and run all cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:114 +msgid "restart the kernel, then re-run the whole notebook (no confirmation dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:120 +msgid "confirm restart kernel and run all cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:121 +msgid "restart the kernel, then re-run the whole notebook (with dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:127 +msgid "restart kernel and clear output" +msgstr "" + +#: notebook/static/notebook/js/actions.js:128 +msgid "restart the kernel and clear all output (no confirmation dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:134 +msgid "confirm restart kernel and clear output" +msgstr "" + +#: notebook/static/notebook/js/actions.js:135 +msgid "restart the kernel and clear all output (with dialog)" +msgstr "" + +#: notebook/static/notebook/js/actions.js:142 +#: notebook/static/notebook/js/actions.js:143 +msgid "interrupt the kernel" +msgstr "" + +#: notebook/static/notebook/js/actions.js:150 +msgid "run cell and select next" +msgstr "" + +#: notebook/static/notebook/js/actions.js:152 +msgid "run cell, select below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:159 +#: notebook/static/notebook/js/actions.js:160 +msgid "run selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:167 +#: notebook/static/notebook/js/actions.js:168 +msgid "run cell and insert below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:175 +#: notebook/static/notebook/js/actions.js:176 +msgid "run all cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:183 +#: notebook/static/notebook/js/actions.js:184 +msgid "run all cells above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:190 +#: notebook/static/notebook/js/actions.js:191 +msgid "run all cells below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:197 +#: notebook/static/notebook/js/actions.js:198 +msgid "enter command mode" +msgstr "" + +#: notebook/static/notebook/js/actions.js:205 +#: notebook/static/notebook/js/actions.js:206 +msgid "insert image" +msgstr "" + +#: notebook/static/notebook/js/actions.js:213 +#: notebook/static/notebook/js/actions.js:214 +msgid "cut cell attachments" +msgstr "" + +#: notebook/static/notebook/js/actions.js:221 +#: notebook/static/notebook/js/actions.js:222 +msgid "copy cell attachments" +msgstr "" + +#: notebook/static/notebook/js/actions.js:229 +#: notebook/static/notebook/js/actions.js:230 +msgid "paste cell attachments" +msgstr "" + +#: notebook/static/notebook/js/actions.js:237 +#: notebook/static/notebook/js/actions.js:238 +msgid "split cell at cursor" +msgstr "" + +#: notebook/static/notebook/js/actions.js:245 +#: notebook/static/notebook/js/actions.js:246 +msgid "enter edit mode" +msgstr "" + +#: notebook/static/notebook/js/actions.js:253 +msgid "select previous cell" +msgstr "" + +#: notebook/static/notebook/js/actions.js:254 +msgid "select cell above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:265 +msgid "select next cell" +msgstr "" + +#: notebook/static/notebook/js/actions.js:266 +msgid "select cell below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:277 +msgid "extend selection above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:278 +msgid "extend selected cells above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:289 +msgid "extend selection below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:290 +msgid "extend selected cells below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:301 +#: notebook/static/notebook/js/actions.js:302 +msgid "cut selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:312 +#: notebook/static/notebook/js/actions.js:313 +msgid "copy selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:327 +#: notebook/static/notebook/js/actions.js:328 +msgid "paste cells above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:335 +#: notebook/static/notebook/js/actions.js:336 +msgid "paste cells below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:344 +#: notebook/static/notebook/js/actions.js:345 +msgid "insert cell above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:354 +#: notebook/static/notebook/js/actions.js:355 +msgid "insert cell below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:365 +#: notebook/static/notebook/js/actions.js:366 +msgid "change cell to code" +msgstr "" + +#: notebook/static/notebook/js/actions.js:373 +#: notebook/static/notebook/js/actions.js:374 +msgid "change cell to markdown" +msgstr "" + +#: notebook/static/notebook/js/actions.js:381 +#: notebook/static/notebook/js/actions.js:382 +msgid "change cell to raw" +msgstr "" + +#: notebook/static/notebook/js/actions.js:389 +#: notebook/static/notebook/js/actions.js:390 +msgid "change cell to heading 1" +msgstr "" + +#: notebook/static/notebook/js/actions.js:397 +#: notebook/static/notebook/js/actions.js:398 +msgid "change cell to heading 2" +msgstr "" + +#: notebook/static/notebook/js/actions.js:405 +#: notebook/static/notebook/js/actions.js:406 +msgid "change cell to heading 3" +msgstr "" + +#: notebook/static/notebook/js/actions.js:413 +#: notebook/static/notebook/js/actions.js:414 +msgid "change cell to heading 4" +msgstr "" + +#: notebook/static/notebook/js/actions.js:421 +#: notebook/static/notebook/js/actions.js:422 +msgid "change cell to heading 5" +msgstr "" + +#: notebook/static/notebook/js/actions.js:429 +#: notebook/static/notebook/js/actions.js:430 +msgid "change cell to heading 6" +msgstr "" + +#: notebook/static/notebook/js/actions.js:437 +msgid "toggle cell output" +msgstr "" + +#: notebook/static/notebook/js/actions.js:438 +msgid "toggle output of selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:445 +msgid "toggle cell scrolling" +msgstr "" + +#: notebook/static/notebook/js/actions.js:446 +msgid "toggle output scrolling of selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:453 +msgid "clear cell output" +msgstr "" + +#: notebook/static/notebook/js/actions.js:454 +msgid "clear output of selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:460 +msgid "move cells down" +msgstr "" + +#: notebook/static/notebook/js/actions.js:461 +msgid "move selected cells down" +msgstr "" + +#: notebook/static/notebook/js/actions.js:469 +msgid "move cells up" +msgstr "" + +#: notebook/static/notebook/js/actions.js:470 +msgid "move selected cells up" +msgstr "" + +#: notebook/static/notebook/js/actions.js:478 +#: notebook/static/notebook/js/actions.js:479 +msgid "toggle line numbers" +msgstr "" + +#: notebook/static/notebook/js/actions.js:486 +#: notebook/static/notebook/js/actions.js:487 +msgid "show keyboard shortcuts" +msgstr "" + +#: notebook/static/notebook/js/actions.js:494 +msgid "delete cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:495 +msgid "delete selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:502 +#: notebook/static/notebook/js/actions.js:503 +msgid "undo cell deletion" +msgstr "" + +#: notebook/static/notebook/js/actions.js:512 +msgid "merge cell with previous cell" +msgstr "" + +#: notebook/static/notebook/js/actions.js:513 +msgid "merge cell above" +msgstr "" + +#: notebook/static/notebook/js/actions.js:519 +msgid "merge cell with next cell" +msgstr "" + +#: notebook/static/notebook/js/actions.js:520 +msgid "merge cell below" +msgstr "" + +#: notebook/static/notebook/js/actions.js:527 +#: notebook/static/notebook/js/actions.js:528 +msgid "merge selected cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:535 +msgid "merge cells" +msgstr "" + +#: notebook/static/notebook/js/actions.js:536 +msgid "merge selected cells, or current cell with cell below if only one cell is selected" +msgstr "" + +#: notebook/static/notebook/js/actions.js:549 +msgid "show command pallette" +msgstr "" + +#: notebook/static/notebook/js/actions.js:550 +msgid "open the command palette" +msgstr "" + +#: notebook/static/notebook/js/actions.js:557 +msgid "toggle all line numbers" +msgstr "" + +#: notebook/static/notebook/js/actions.js:558 +msgid "toggles line numbers in all cells, and persist the setting" +msgstr "" + +#: notebook/static/notebook/js/actions.js:569 +msgid "show all line numbers" +msgstr "" + +#: notebook/static/notebook/js/actions.js:570 +msgid "show line numbers in all cells, and persist the setting" +msgstr "" + +#: notebook/static/notebook/js/actions.js:579 +msgid "hide all line numbers" +msgstr "" + +#: notebook/static/notebook/js/actions.js:580 +msgid "hide line numbers in all cells, and persist the setting" +msgstr "" + +#: notebook/static/notebook/js/actions.js:589 +msgid "toggle header" +msgstr "" + +#: notebook/static/notebook/js/actions.js:590 +msgid "switch between showing and hiding the header" +msgstr "" + +#: notebook/static/notebook/js/actions.js:605 +#: notebook/static/notebook/js/actions.js:606 +msgid "show the header" +msgstr "" + +#: notebook/static/notebook/js/actions.js:615 +#: notebook/static/notebook/js/actions.js:616 +msgid "hide the header" +msgstr "" + +#: notebook/static/notebook/js/actions.js:646 +msgid "toggle toolbar" +msgstr "" + +#: notebook/static/notebook/js/actions.js:647 +msgid "switch between showing and hiding the toolbar" +msgstr "" + +#: notebook/static/notebook/js/actions.js:660 +#: notebook/static/notebook/js/actions.js:661 +msgid "show the toolbar" +msgstr "" + +#: notebook/static/notebook/js/actions.js:669 +#: notebook/static/notebook/js/actions.js:670 +msgid "hide the toolbar" +msgstr "" + +#: notebook/static/notebook/js/actions.js:678 +#: notebook/static/notebook/js/actions.js:679 +msgid "close the pager" +msgstr "" + +#: notebook/static/notebook/js/actions.js:704 +msgid "ignore" +msgstr "" + +#: notebook/static/notebook/js/actions.js:710 +#: notebook/static/notebook/js/actions.js:711 +msgid "move cursor up" +msgstr "" + +#: notebook/static/notebook/js/actions.js:731 +#: notebook/static/notebook/js/actions.js:732 +msgid "move cursor down" +msgstr "" + +#: notebook/static/notebook/js/actions.js:750 +#: notebook/static/notebook/js/actions.js:751 +msgid "scroll notebook down" +msgstr "" + +#: notebook/static/notebook/js/actions.js:760 +#: notebook/static/notebook/js/actions.js:761 +msgid "scroll notebook up" +msgstr "" + +#: notebook/static/notebook/js/actions.js:770 +msgid "scroll cell center" +msgstr "" + +#: notebook/static/notebook/js/actions.js:771 +msgid "Scroll the current cell to the center" +msgstr "" + +#: notebook/static/notebook/js/actions.js:781 +msgid "scroll cell top" +msgstr "" + +#: notebook/static/notebook/js/actions.js:782 +msgid "Scroll the current cell to the top" +msgstr "" + +#: notebook/static/notebook/js/actions.js:792 +msgid "duplicate notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:793 +msgid "Create and open a copy of the current notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:799 +msgid "trust notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:800 +msgid "Trust the current notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:806 +msgid "rename notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:807 +msgid "Rename the current notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:813 +msgid "toggle all cells output collapsed" +msgstr "" + +#: notebook/static/notebook/js/actions.js:814 +msgid "Toggle the hidden state of all output areas" +msgstr "" + +#: notebook/static/notebook/js/actions.js:820 +msgid "toggle all cells output scrolled" +msgstr "" + +#: notebook/static/notebook/js/actions.js:821 +msgid "Toggle the scrolling state of all output areas" +msgstr "" + +#: notebook/static/notebook/js/actions.js:828 +msgid "clear all cells output" +msgstr "" + +#: notebook/static/notebook/js/actions.js:829 +msgid "Clear the content of all the outputs" +msgstr "" + +#: notebook/static/notebook/js/actions.js:835 +msgid "save notebook" +msgstr "" + +#: notebook/static/notebook/js/actions.js:836 +msgid "Save and Checkpoint" +msgstr "" + +#: notebook/static/notebook/js/cell.js:79 +msgid "Warning: accessing Cell.cm_config directly is deprecated." +msgstr "" + +#: notebook/static/notebook/js/cell.js:763 +#, python-format +msgid "Unrecognized cell type: %s" +msgstr "" + +#: notebook/static/notebook/js/cell.js:777 +msgid "Unrecognized cell type" +msgstr "" + +#: notebook/static/notebook/js/celltoolbar.js:296 +#, python-format +msgid "Error in cell toolbar callback %s" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:53 +#, python-format +msgid "Clipboard types: %s" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:96 +msgid "Dialog for paste from system clipboard" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:109 +msgid "Ctrl-V" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:111 +msgid "Cmd-V" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:113 +#, python-format +msgid "Press %s again to paste" +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:116 +msgid "Why is this needed? " +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:118 +msgid "We can't get paste events in this browser without a text box. " +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:119 +msgid "There's an invisible text box focused in this dialog." +msgstr "" + +#: notebook/static/notebook/js/clipboard.js:125 +#, python-format +msgid "%s to paste" +msgstr "" + +#: notebook/static/notebook/js/codecell.js:310 +msgid "Can't execute cell since kernel is not set." +msgstr "" + +#: notebook/static/notebook/js/codecell.js:472 +msgid "In" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:269 +#, python-format +msgid "Could not find a kernel matching %s. Please select a kernel:" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:278 +msgid "Continue Without Kernel" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:278 +msgid "Set Kernel" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:281 +msgid "Kernel not found" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:319 +#: notebook/static/tree/js/newnotebook.js:99 +msgid "Creating Notebook Failed" +msgstr "" + +#: notebook/static/notebook/js/kernelselector.js:320 +#: notebook/static/tree/js/notebooklist.js:1360 +#, python-format +msgid "The error was: %s" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:54 +msgid "Run" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:76 +msgid "Code" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:77 +msgid "Markdown" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:78 +msgid "Raw NBConvert" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:79 +msgid "Heading" +msgstr "" + +#: notebook/static/notebook/js/maintoolbar.js:115 +msgid "unrecognized cell type:" +msgstr "" + +#: notebook/static/notebook/js/mathjaxutils.js:45 +#, python-format +msgid "Failed to retrieve MathJax from '%s'" +msgstr "" + +#: notebook/static/notebook/js/mathjaxutils.js:47 +msgid "Math/LaTeX rendering will be disabled." +msgstr "" + +#: notebook/static/notebook/js/menubar.js:220 +msgid "Trusted Notebook" +msgstr "" + +#: notebook/static/notebook/js/menubar.js:226 +msgid "Trust Notebook" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:16 +#: notebook/static/notebook/js/menubar.js:383 +msgid "None" +msgstr "" + +#: notebook/static/notebook/js/menubar.js:406 +msgid "No checkpoints" +msgstr "" + +#: notebook/static/notebook/js/menubar.js:465 +msgid "Opens in a new window" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:431 +msgid "Autosave in progress, latest changes may be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:433 +msgid "Unsaved changes will be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:438 +msgid "The Kernel is busy, outputs may be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:461 +msgid "This notebook is version %1$s, but we only fully support up to %2$s." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:463 +msgid "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:470 +msgid "Restart and Run All Cells" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:471 +msgid "Restart and Clear All Outputs" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:472 +msgid "Restart" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:473 +msgid "Continue Running" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:474 +msgid "Reload" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:476 +msgid "Overwrite" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:477 +msgid "Trust" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:478 +msgid "Revert" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:483 +msgid "Newer Notebook" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:1548 +msgid "Use markdown headings" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:1550 +msgid "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:1553 +msgid "## This is a level 2 heading" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2248 +msgid "Restart kernel and re-run the whole notebook?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2250 +msgid "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2275 +msgid "Restart kernel and clear all output?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2277 +msgid "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2322 +msgid "Restart kernel?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2324 +msgid "Do you want to restart the current kernel? All variables will be lost." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2734 +msgid "Notebook changed" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2735 +msgid "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page) ?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2782 +#: notebook/static/notebook/js/notebook.js:2990 +msgid "Notebook validation failed" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2785 +msgid "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2836 +msgid "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: " +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2840 +msgid "here" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2848 +msgid "Trust this notebook?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2981 +msgid "Notebook failed to load" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2983 +msgid "The error was: " +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2987 +msgid "See the error console for details." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2995 +msgid "The notebook also failed validation:" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:2997 +msgid "An invalid notebook may not function properly. The validation error was:" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3036 +#, python-format +msgid "This notebook has been converted from an older notebook format to the current notebook format v(%s)." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3038 +#, python-format +msgid "This notebook has been converted from a newer notebook format to the current notebook format v(%s)." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3046 +msgid "The next time you save this notebook, the current notebook format will be used." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3051 +msgid "Older versions of Jupyter may not be able to read the new format." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3053 +msgid "Some features of the original notebook may not be available." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3056 +msgid "To preserve the original version, close the notebook without saving it." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3061 +msgid "Notebook converted" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3083 +msgid "(No name)" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3131 +#, python-format +msgid "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3142 +msgid "Error loading notebook" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3243 +msgid "Are you sure you want to revert the notebook to the latest checkpoint?" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3246 +msgid "This cannot be undone." +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3249 +msgid "The checkpoint was last updated at:" +msgstr "" + +#: notebook/static/notebook/js/notebook.js:3260 +msgid "Revert notebook to checkpoint" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:77 +#: notebook/static/notebook/js/tour.js:61 +#: notebook/static/notebook/js/tour.js:67 +msgid "Edit Mode" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:84 +#: notebook/static/notebook/js/notificationarea.js:88 +#: notebook/static/notebook/js/tour.js:54 +msgid "Command Mode" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:95 +msgid "Kernel Created" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:99 +msgid "Connecting to kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:103 +msgid "Not Connected" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:106 +msgid "click to reconnect" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:115 +msgid "Restarting kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:129 +msgid "Kernel Restarting" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:130 +msgid "The kernel appears to have died. It will restart automatically." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:140 +#: notebook/static/notebook/js/notificationarea.js:198 +#: notebook/static/notebook/js/notificationarea.js:218 +msgid "Dead kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:141 +#: notebook/static/notebook/js/notificationarea.js:219 +#: notebook/static/notebook/js/notificationarea.js:266 +msgid "Kernel Dead" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:145 +msgid "Interrupting kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:151 +msgid "No Connection to Kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:161 +msgid "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:166 +msgid "Connection failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:179 +msgid "No kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:180 +msgid "Kernel is not running" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:187 +msgid "Don't Restart" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:187 +msgid "Try Restarting Now" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:191 +msgid "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:225 +msgid "No Kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:252 +msgid "Failed to start the kernel" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:272 +#: notebook/static/notebook/js/notificationarea.js:292 +#: notebook/static/notebook/js/notificationarea.js:306 +msgid "Kernel Busy" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:273 +msgid "Kernel starting, please wait..." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:279 +#: notebook/static/notebook/js/notificationarea.js:286 +msgid "Kernel Idle" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:280 +msgid "Kernel ready" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:297 +msgid "Using kernel: " +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:298 +msgid "Only candidate for language: %1$s was %2$s." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:319 +msgid "Loading notebook" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:322 +msgid "Notebook loaded" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:325 +msgid "Saving notebook" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:328 +msgid "Notebook saved" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:331 +msgid "Notebook save failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:334 +msgid "Notebook copy failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:339 +msgid "Checkpoint created" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:347 +msgid "Checkpoint failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:350 +msgid "Checkpoint deleted" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:353 +msgid "Checkpoint delete failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:356 +msgid "Restoring to checkpoint..." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:359 +msgid "Checkpoint restore failed" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:364 +msgid "Autosave disabled" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:367 +#, python-format +msgid "Saving every %d sec." +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:383 +msgid "Trusted" +msgstr "" + +#: notebook/static/notebook/js/notificationarea.js:385 +msgid "Not Trusted" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:75 +msgid "click to expand output" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:79 +msgid "click to expand output; double click to hide output" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:167 +msgid "click to unscroll output; double click to hide" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:174 +msgid "click to scroll output; double click to hide" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:422 +msgid "Javascript error adding output!" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:427 +msgid "See your browser Javascript console for more details." +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:468 +#, python-format +msgid "Out[%d]:" +msgstr "" + +#: notebook/static/notebook/js/outputarea.js:577 +#, python-format +msgid "Unrecognized output: %s" +msgstr "" + +#: notebook/static/notebook/js/pager.js:36 +msgid "Open the pager in an external window" +msgstr "" + +#: notebook/static/notebook/js/pager.js:45 +msgid "Close the pager" +msgstr "" + +#: notebook/static/notebook/js/pager.js:148 +msgid "Jupyter Pager" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:39 +#: notebook/static/notebook/js/quickhelp.js:49 +#: notebook/static/notebook/js/quickhelp.js:50 +msgid "go to cell start" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:40 +#: notebook/static/notebook/js/quickhelp.js:51 +#: notebook/static/notebook/js/quickhelp.js:52 +msgid "go to cell end" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:41 +#: notebook/static/notebook/js/quickhelp.js:53 +msgid "go one word left" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:42 +#: notebook/static/notebook/js/quickhelp.js:54 +msgid "go one word right" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:43 +#: notebook/static/notebook/js/quickhelp.js:55 +msgid "delete word before" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:44 +#: notebook/static/notebook/js/quickhelp.js:56 +msgid "delete word after" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:61 +msgid "code completion or indent" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:62 +msgid "tooltip" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:63 +msgid "indent" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:64 +msgid "dedent" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:65 +msgid "select all" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:66 +msgid "undo" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:67 +#: notebook/static/notebook/js/quickhelp.js:68 +msgid "redo" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:102 +#: notebook/static/notebook/js/quickhelp.js:243 +msgid "Shift" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:103 +msgid "Alt" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:104 +msgid "Up" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:105 +msgid "Down" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:106 +msgid "Left" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:107 +msgid "Right" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:108 +#: notebook/static/notebook/js/quickhelp.js:246 +msgid "Tab" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:109 +msgid "Caps Lock" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:110 +#: notebook/static/notebook/js/quickhelp.js:269 +msgid "Esc" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:111 +msgid "Ctrl" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:112 +#: notebook/static/notebook/js/quickhelp.js:290 +msgid "Enter" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:113 +msgid "Page Up" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:114 +#: notebook/static/notebook/js/quickhelp.js:130 +msgid "Page Down" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:115 +msgid "Home" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:116 +msgid "End" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:117 +#: notebook/static/notebook/js/quickhelp.js:245 +msgid "Space" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:118 +msgid "Backspace" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:119 +msgid "Minus" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:130 +msgid "PageUp" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:197 +msgid "The Jupyter Notebook has two different keyboard input modes." +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:199 +msgid "Edit mode allows you to type code or text into a cell and is indicated by a green cell border." +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:201 +msgid "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin." +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:222 +#: notebook/static/notebook/js/tooltip.js:58 +#: notebook/static/notebook/js/tooltip.js:69 +msgid "Close" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:225 +msgid "Keyboard shortcuts" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:240 +msgid "Command" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:241 +msgid "Control" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:242 +msgid "Option" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:244 +msgid "Return" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:270 +#, python-format +msgid "Command Mode (press %s to enable)" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:272 +msgid "Edit Shortcuts" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:275 +msgid "edit command-mode keyboard shortcuts" +msgstr "" + +#: notebook/static/notebook/js/quickhelp.js:292 +#, python-format +msgid "Edit Mode (press %s to enable)" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:49 +msgid "Autosave Failed!" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:71 +#: notebook/static/tree/js/notebooklist.js:846 +#: notebook/static/tree/js/notebooklist.js:859 +msgid "Rename" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:78 +#: notebook/static/tree/js/notebooklist.js:837 +msgid "Enter a new notebook name:" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:86 +msgid "Rename Notebook" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:98 +msgid "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:103 +msgid "Renaming..." +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:109 +msgid "Unknown error" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:178 +msgid "no checkpoint" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:193 +#, python-format +msgid "Last Checkpoint: %s" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:217 +msgid "(unsaved changes)" +msgstr "" + +#: notebook/static/notebook/js/savewidget.js:219 +msgid "(autosaved)" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:74 +#, python-format +msgid "Warning: too many matches (%d). Some changes might not be shown or applied." +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:77 +#, python-format +msgid "%d match" +msgid_plural "%d matches" +msgstr[0] "" +msgstr[1] "" + +#: notebook/static/notebook/js/searchandreplace.js:145 +msgid "More than 100 matches, aborting" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:166 +msgid "Use regex (JavaScript regex syntax)" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:174 +msgid "Replace in selected cells" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:181 +msgid "Match case" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:187 +msgid "Find" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:203 +msgid "Replace" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:255 +msgid "No matches, invalid or empty regular expression" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:370 +msgid "Replace All" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:374 +msgid "Find and Replace" +msgstr "" + +#: notebook/static/notebook/js/searchandreplace.js:400 +#: notebook/static/notebook/js/searchandreplace.js:401 +msgid "find and replace" +msgstr "" + +#: notebook/static/notebook/js/textcell.js:551 +msgid "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified." +msgstr "" + +#: notebook/static/notebook/js/tooltip.js:41 +msgid "Grow the tooltip vertically (press shift-tab twice)" +msgstr "" + +#: notebook/static/notebook/js/tooltip.js:48 +msgid "show the current docstring in pager (press shift-tab 4 times)" +msgstr "" + +#: notebook/static/notebook/js/tooltip.js:49 +msgid "Open in Pager" +msgstr "" + +#: notebook/static/notebook/js/tooltip.js:68 +msgid "Tooltip will linger for 10 seconds while you type" +msgstr "" + +#: notebook/static/notebook/js/tour.js:27 +msgid "Welcome to the Notebook Tour" +msgstr "" + +#: notebook/static/notebook/js/tour.js:30 +msgid "You can use the left and right arrow keys to go backwards and forwards." +msgstr "" + +#: notebook/static/notebook/js/tour.js:33 +msgid "Filename" +msgstr "" + +#: notebook/static/notebook/js/tour.js:35 +msgid "Click here to change the filename for this notebook." +msgstr "" + +#: notebook/static/notebook/js/tour.js:39 +msgid "Notebook Menubar" +msgstr "" + +#: notebook/static/notebook/js/tour.js:40 +msgid "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with." +msgstr "" + +#: notebook/static/notebook/js/tour.js:44 +msgid "Notebook Toolbar" +msgstr "" + +#: notebook/static/notebook/js/tour.js:45 +msgid "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information." +msgstr "" + +#: notebook/static/notebook/js/tour.js:48 +msgid "Mode Indicator" +msgstr "" + +#: notebook/static/notebook/js/tour.js:50 +msgid "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in." +msgstr "" + +#: notebook/static/notebook/js/tour.js:58 +msgid "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area." +msgstr "" + +#: notebook/static/notebook/js/tour.js:64 +msgid "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode." +msgstr "" + +#: notebook/static/notebook/js/tour.js:70 +msgid "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell." +msgstr "" + +#: notebook/static/notebook/js/tour.js:73 +msgid "Back to Command Mode" +msgstr "" + +#: notebook/static/notebook/js/tour.js:76 +msgid "Pressing Esc or clicking outside of the input text area takes you back to Command Mode." +msgstr "" + +#: notebook/static/notebook/js/tour.js:79 +msgid "Keyboard Shortcuts" +msgstr "" + +#: notebook/static/notebook/js/tour.js:91 +msgid "You can click here to get a list of all of the keyboard shortcuts." +msgstr "" + +#: notebook/static/notebook/js/tour.js:94 +#: notebook/static/notebook/js/tour.js:100 +msgid "Kernel Indicator" +msgstr "" + +#: notebook/static/notebook/js/tour.js:97 +msgid "This is the Kernel indicator. It looks like this when the Kernel is idle." +msgstr "" + +#: notebook/static/notebook/js/tour.js:103 +msgid "The Kernel indicator looks like this when the Kernel is busy." +msgstr "" + +#: notebook/static/notebook/js/tour.js:107 +msgid "Interrupting the Kernel" +msgstr "" + +#: notebook/static/notebook/js/tour.js:109 +msgid "To cancel a computation in progress, you can click here." +msgstr "" + +#: notebook/static/notebook/js/tour.js:114 +msgid "Notification Area" +msgstr "" + +#: notebook/static/notebook/js/tour.js:115 +msgid "Messages in response to user actions (Save, Interrupt, etc.) appear here." +msgstr "" + +#: notebook/static/notebook/js/tour.js:117 +msgid "End of Tour" +msgstr "" + +#: notebook/static/notebook/js/tour.js:120 +msgid "This concludes the Jupyter Notebook User Interface Tour." +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/attachments.js:32 +msgid "Edit Attachments" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/default.js:19 +msgid "Cell" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/default.js:29 +#: notebook/static/notebook/js/celltoolbarpresets/default.js:47 +msgid "Edit Metadata" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:22 +msgid "Custom" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:32 +msgid "Set the MIME type of the raw cell:" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:40 +msgid "Raw Cell MIME Type" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:74 +msgid "Raw NBConvert Format" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/rawcell.js:81 +msgid "Raw Cell Format" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:15 +msgid "Slide" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:16 +msgid "Sub-Slide" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:17 +msgid "Fragment" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:18 +msgid "Skip" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:19 +msgid "Notes" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:35 +msgid "Slide Type" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/slideshow.js:41 +msgid "Slideshow" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/tags.js:133 +msgid "Add tag" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/tags.js:163 +msgid "Edit the list of tags below. All whitespace is treated as tag separators." +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/tags.js:172 +msgid "Edit the tags" +msgstr "" + +#: notebook/static/notebook/js/celltoolbarpresets/tags.js:186 +msgid "Edit Tags" +msgstr "" + +#: notebook/static/tree/js/kernellist.js:86 +#: notebook/static/tree/js/terminallist.js:105 +msgid "Shutdown" +msgstr "" + +#: notebook/static/tree/js/newnotebook.js:70 +#, python-format +msgid "Create a new notebook with %s" +msgstr "" + +#: notebook/static/tree/js/newnotebook.js:101 +msgid "An error occurred while creating a new notebook." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:122 +msgid "Creating File Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:124 +msgid "An error occurred while creating a new file." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:142 +msgid "Creating Folder Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:144 +msgid "An error occurred while creating a new folder." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:271 +msgid "Failed to read file" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:272 +#, python-format +msgid "Failed to read file %s" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:283 +#, python-format +msgid "The file size is %d MB. Do you still want to upload it?" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:286 +msgid "Large file size warning" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:355 +msgid "Server error: " +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:390 +msgid "The notebook list is empty." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:463 +msgid "Click here to rename, delete, etc." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:503 +msgid "Running" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:835 +msgid "Enter a new file name:" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:836 +msgid "Enter a new directory name:" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:838 +msgid "Enter a new name:" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:843 +msgid "Rename file" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:844 +msgid "Rename directory" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:845 +msgid "Rename notebook" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:859 +msgid "Move" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:875 +msgid "An error occurred while renaming \"%1$s\" to \"%2$s\"." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:878 +msgid "Rename Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:927 +#, python-format +msgid "Enter a new destination directory path for this item:" +msgid_plural "Enter a new destination directory path for these %d items:" +msgstr[0] "" +msgstr[1] "" + +#: notebook/static/tree/js/notebooklist.js:940 +#, python-format +msgid "Move an Item" +msgid_plural "Move %d Items" +msgstr[0] "" +msgstr[1] "" + +#: notebook/static/tree/js/notebooklist.js:959 +msgid "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\"." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:961 +msgid "Move Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1007 +#, python-format +msgid "Are you sure you want to permanently delete: \"%s\"?" +msgid_plural "Are you sure you want to permanently delete the %d files or folders selected?" +msgstr[0] "" +msgstr[1] "" + +#: notebook/static/tree/js/notebooklist.js:1035 +#, python-format +msgid "An error occurred while deleting \"%s\"." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1037 +msgid "Delete Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1078 +#, python-format +msgid "Are you sure you want to duplicate: \"%s\"?" +msgid_plural "Are you sure you want to duplicate the %d files selected?" +msgstr[0] "" +msgstr[1] "" + +#: notebook/static/tree/js/notebooklist.js:1088 +msgid "Duplicate" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1102 +#, python-format +msgid "An error occurred while duplicating \"%s\"." +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1104 +msgid "Duplicate Failed" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1323 +msgid "Upload" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1332 +msgid "Invalid file name" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1333 +msgid "File names must be at least one character and not start with a period" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1362 +msgid "Cannot upload invalid Notebook" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1395 +#, python-format +msgid "There is already a file named \"%s\". Do you want to replace it?" +msgstr "" + +#: notebook/static/tree/js/notebooklist.js:1397 +msgid "Replace file" +msgstr "" + diff --git a/notebook/i18n/nbui.pot b/notebook/i18n/nbui.pot new file mode 100644 index 000000000..11c30aac6 --- /dev/null +++ b/notebook/i18n/nbui.pot @@ -0,0 +1,731 @@ +# Translations template for Jupyter. +# Copyright (C) 2017 ORGANIZATION +# This file is distributed under the same license as the Jupyter project. +# FIRST AUTHOR , 2017. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Jupyter VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2017-07-07 12:48-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.3.4\n" + +#: notebook/templates/404.html:3 +msgid "You are requesting a page that does not exist!" +msgstr "" + +#: notebook/templates/edit.html:37 +msgid "current mode" +msgstr "" + +#: notebook/templates/edit.html:48 notebook/templates/notebook.html:78 +msgid "File" +msgstr "" + +#: notebook/templates/edit.html:50 notebook/templates/tree.html:57 +msgid "New" +msgstr "" + +#: notebook/templates/edit.html:51 +msgid "Save" +msgstr "" + +#: notebook/templates/edit.html:52 notebook/templates/tree.html:36 +msgid "Rename" +msgstr "" + +#: notebook/templates/edit.html:53 notebook/templates/tree.html:38 +msgid "Download" +msgstr "" + +#: notebook/templates/edit.html:56 notebook/templates/notebook.html:131 +#: notebook/templates/tree.html:41 +msgid "Edit" +msgstr "" + +#: notebook/templates/edit.html:58 +msgid "Find" +msgstr "" + +#: notebook/templates/edit.html:59 +msgid "Find & Replace" +msgstr "" + +#: notebook/templates/edit.html:61 +msgid "Key Map" +msgstr "" + +#: notebook/templates/edit.html:62 +msgid "Default" +msgstr "" + +#: notebook/templates/edit.html:63 +msgid "Sublime Text" +msgstr "" + +#: notebook/templates/edit.html:68 notebook/templates/notebook.html:159 +#: notebook/templates/tree.html:40 +msgid "View" +msgstr "" + +#: notebook/templates/edit.html:70 notebook/templates/notebook.html:162 +msgid "Show/Hide the logo and notebook title (above menu bar)" +msgstr "" + +#: notebook/templates/edit.html:71 notebook/templates/notebook.html:163 +msgid "Toggle Header" +msgstr "" + +#: notebook/templates/edit.html:72 notebook/templates/notebook.html:171 +msgid "Toggle Line Numbers" +msgstr "" + +#: notebook/templates/edit.html:75 +msgid "Language" +msgstr "" + +#: notebook/templates/error.html:23 +msgid "The error was:" +msgstr "" + +#: notebook/templates/login.html:24 +msgid "Password or token:" +msgstr "" + +#: notebook/templates/login.html:26 +msgid "Password:" +msgstr "" + +#: notebook/templates/login.html:31 +msgid "Log in" +msgstr "" + +#: notebook/templates/login.html:39 +msgid "No login available, you shouldn't be seeing this page." +msgstr "" + +#: notebook/templates/logout.html:24 +#, python-format +msgid "Proceed to the dashboard" +msgstr "" + +#: notebook/templates/logout.html:26 +#, python-format +msgid "Proceed to the login page" +msgstr "" + +#: notebook/templates/notebook.html:62 +msgid "Menu" +msgstr "" + +#: notebook/templates/notebook.html:65 notebook/templates/notebook.html:254 +msgid "Kernel" +msgstr "" + +#: notebook/templates/notebook.html:68 +msgid "This notebook is read-only" +msgstr "" + +#: notebook/templates/notebook.html:81 +msgid "New Notebook" +msgstr "" + +#: notebook/templates/notebook.html:85 +msgid "Opens a new window with the Dashboard view" +msgstr "" + +#: notebook/templates/notebook.html:86 +msgid "Open..." +msgstr "" + +#: notebook/templates/notebook.html:90 +msgid "Open a copy of this notebook's contents and start a new kernel" +msgstr "" + +#: notebook/templates/notebook.html:91 +msgid "Make a Copy..." +msgstr "" + +#: notebook/templates/notebook.html:92 +msgid "Rename..." +msgstr "" + +#: notebook/templates/notebook.html:93 +msgid "Save and Checkpoint" +msgstr "" + +#: notebook/templates/notebook.html:96 +msgid "Revert to Checkpoint" +msgstr "" + +#: notebook/templates/notebook.html:106 +msgid "Print Preview" +msgstr "" + +#: notebook/templates/notebook.html:107 +msgid "Download as" +msgstr "" + +#: notebook/templates/notebook.html:109 +msgid "Notebook (.ipynb)" +msgstr "" + +#: notebook/templates/notebook.html:110 +msgid "Script" +msgstr "" + +#: notebook/templates/notebook.html:111 +msgid "HTML (.html)" +msgstr "" + +#: notebook/templates/notebook.html:112 +msgid "Markdown (.md)" +msgstr "" + +#: notebook/templates/notebook.html:113 +msgid "reST (.rst)" +msgstr "" + +#: notebook/templates/notebook.html:114 +msgid "LaTeX (.tex)" +msgstr "" + +#: notebook/templates/notebook.html:115 +msgid "PDF via LaTeX (.pdf)" +msgstr "" + +#: notebook/templates/notebook.html:118 +msgid "Deploy as" +msgstr "" + +#: notebook/templates/notebook.html:123 +msgid "Trust the output of this notebook" +msgstr "" + +#: notebook/templates/notebook.html:124 +msgid "Trust Notebook" +msgstr "" + +#: notebook/templates/notebook.html:127 +msgid "Shutdown this notebook's kernel, and close this window" +msgstr "" + +#: notebook/templates/notebook.html:128 +msgid "Close and Halt" +msgstr "" + +#: notebook/templates/notebook.html:133 +msgid "Cut Cells" +msgstr "" + +#: notebook/templates/notebook.html:134 +msgid "Copy Cells" +msgstr "" + +#: notebook/templates/notebook.html:135 +msgid "Paste Cells Above" +msgstr "" + +#: notebook/templates/notebook.html:136 +msgid "Paste Cells Below" +msgstr "" + +#: notebook/templates/notebook.html:137 +msgid "Paste Cells & Replace" +msgstr "" + +#: notebook/templates/notebook.html:138 +msgid "Delete Cells" +msgstr "" + +#: notebook/templates/notebook.html:139 +msgid "Undo Delete Cells" +msgstr "" + +#: notebook/templates/notebook.html:141 +msgid "Split Cell" +msgstr "" + +#: notebook/templates/notebook.html:142 +msgid "Merge Cell Above" +msgstr "" + +#: notebook/templates/notebook.html:143 +msgid "Merge Cell Below" +msgstr "" + +#: notebook/templates/notebook.html:145 +msgid "Move Cell Up" +msgstr "" + +#: notebook/templates/notebook.html:146 +msgid "Move Cell Down" +msgstr "" + +#: notebook/templates/notebook.html:148 +msgid "Edit Notebook Metadata" +msgstr "" + +#: notebook/templates/notebook.html:150 +msgid "Find and Replace" +msgstr "" + +#: notebook/templates/notebook.html:152 +msgid "Cut Cell Attachments" +msgstr "" + +#: notebook/templates/notebook.html:153 +msgid "Copy Cell Attachments" +msgstr "" + +#: notebook/templates/notebook.html:154 +msgid "Paste Cell Attachments" +msgstr "" + +#: notebook/templates/notebook.html:156 +msgid "Insert Image" +msgstr "" + +#: notebook/templates/notebook.html:166 +msgid "Show/Hide the action icons (below menu bar)" +msgstr "" + +#: notebook/templates/notebook.html:167 +msgid "Toggle Toolbar" +msgstr "" + +#: notebook/templates/notebook.html:170 +msgid "Show/Hide line numbers in cells" +msgstr "" + +#: notebook/templates/notebook.html:174 +msgid "Cell Toolbar" +msgstr "" + +#: notebook/templates/notebook.html:179 +msgid "Insert" +msgstr "" + +#: notebook/templates/notebook.html:182 +msgid "Insert an empty Code cell above the currently active cell" +msgstr "" + +#: notebook/templates/notebook.html:183 +msgid "Insert Cell Above" +msgstr "" + +#: notebook/templates/notebook.html:185 +msgid "Insert an empty Code cell below the currently active cell" +msgstr "" + +#: notebook/templates/notebook.html:186 +msgid "Insert Cell Below" +msgstr "" + +#: notebook/templates/notebook.html:189 +msgid "Cell" +msgstr "" + +#: notebook/templates/notebook.html:191 +msgid "Run this cell, and move cursor to the next one" +msgstr "" + +#: notebook/templates/notebook.html:192 +msgid "Run Cells" +msgstr "" + +#: notebook/templates/notebook.html:193 +msgid "Run this cell, select below" +msgstr "" + +#: notebook/templates/notebook.html:194 +msgid "Run Cells and Select Below" +msgstr "" + +#: notebook/templates/notebook.html:195 +msgid "Run this cell, insert below" +msgstr "" + +#: notebook/templates/notebook.html:196 +msgid "Run Cells and Insert Below" +msgstr "" + +#: notebook/templates/notebook.html:197 +msgid "Run all cells in the notebook" +msgstr "" + +#: notebook/templates/notebook.html:198 +msgid "Run All" +msgstr "" + +#: notebook/templates/notebook.html:199 +msgid "Run all cells above (but not including) this cell" +msgstr "" + +#: notebook/templates/notebook.html:200 +msgid "Run All Above" +msgstr "" + +#: notebook/templates/notebook.html:201 +msgid "Run this cell and all cells below it" +msgstr "" + +#: notebook/templates/notebook.html:202 +msgid "Run All Below" +msgstr "" + +#: notebook/templates/notebook.html:205 +msgid "All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells" +msgstr "" + +#: notebook/templates/notebook.html:206 +msgid "Cell Type" +msgstr "" + +#: notebook/templates/notebook.html:209 +msgid "Contents will be sent to the kernel for execution, and output will display in the footer of cell" +msgstr "" + +#: notebook/templates/notebook.html:212 +msgid "Contents will be rendered as HTML and serve as explanatory text" +msgstr "" + +#: notebook/templates/notebook.html:213 notebook/templates/notebook.html:298 +msgid "Markdown" +msgstr "" + +#: notebook/templates/notebook.html:215 +msgid "Contents will pass through nbconvert unmodified" +msgstr "" + +#: notebook/templates/notebook.html:216 +msgid "Raw NBConvert" +msgstr "" + +#: notebook/templates/notebook.html:220 +msgid "Current Outputs" +msgstr "" + +#: notebook/templates/notebook.html:223 +msgid "Hide/Show the output of the current cell" +msgstr "" + +#: notebook/templates/notebook.html:224 notebook/templates/notebook.html:240 +msgid "Toggle" +msgstr "" + +#: notebook/templates/notebook.html:227 +msgid "Scroll the output of the current cell" +msgstr "" + +#: notebook/templates/notebook.html:228 notebook/templates/notebook.html:244 +msgid "Toggle Scrolling" +msgstr "" + +#: notebook/templates/notebook.html:231 +msgid "Clear the output of the current cell" +msgstr "" + +#: notebook/templates/notebook.html:232 notebook/templates/notebook.html:248 +msgid "Clear" +msgstr "" + +#: notebook/templates/notebook.html:236 +msgid "All Output" +msgstr "" + +#: notebook/templates/notebook.html:239 +msgid "Hide/Show the output of all cells" +msgstr "" + +#: notebook/templates/notebook.html:243 +msgid "Scroll the output of all cells" +msgstr "" + +#: notebook/templates/notebook.html:247 +msgid "Clear the output of all cells" +msgstr "" + +#: notebook/templates/notebook.html:257 +msgid "Send Keyboard Interrupt (CTRL-C) to the Kernel" +msgstr "" + +#: notebook/templates/notebook.html:258 +msgid "Interrupt" +msgstr "" + +#: notebook/templates/notebook.html:261 +msgid "Restart the Kernel" +msgstr "" + +#: notebook/templates/notebook.html:262 +msgid "Restart" +msgstr "" + +#: notebook/templates/notebook.html:265 +msgid "Restart the Kernel and clear all output" +msgstr "" + +#: notebook/templates/notebook.html:266 +msgid "Restart & Clear Output" +msgstr "" + +#: notebook/templates/notebook.html:269 +msgid "Restart the Kernel and re-run the notebook" +msgstr "" + +#: notebook/templates/notebook.html:270 +msgid "Restart & Run All" +msgstr "" + +#: notebook/templates/notebook.html:273 +msgid "Reconnect to the Kernel" +msgstr "" + +#: notebook/templates/notebook.html:274 +msgid "Reconnect" +msgstr "" + +#: notebook/templates/notebook.html:282 +msgid "Change kernel" +msgstr "" + +#: notebook/templates/notebook.html:287 +msgid "Help" +msgstr "" + +#: notebook/templates/notebook.html:290 +msgid "A quick tour of the notebook user interface" +msgstr "" + +#: notebook/templates/notebook.html:290 +msgid "User Interface Tour" +msgstr "" + +#: notebook/templates/notebook.html:291 +msgid "Opens a tooltip with all keyboard shortcuts" +msgstr "" + +#: notebook/templates/notebook.html:291 +msgid "Keyboard Shortcuts" +msgstr "" + +#: notebook/templates/notebook.html:292 +msgid "Opens a dialog allowing you to edit Keyboard shortcuts" +msgstr "" + +#: notebook/templates/notebook.html:292 +msgid "Edit Keyboard Shortcuts" +msgstr "" + +#: notebook/templates/notebook.html:297 +msgid "Notebook Help" +msgstr "" + +#: notebook/templates/notebook.html:303 +msgid "Opens in a new window" +msgstr "" + +#: notebook/templates/notebook.html:319 +msgid "About Jupyter Notebook" +msgstr "" + +#: notebook/templates/notebook.html:319 +msgid "About" +msgstr "" + +#: notebook/templates/page.html:114 +msgid "Jupyter Notebook requires JavaScript." +msgstr "" + +#: notebook/templates/page.html:115 +msgid "Please enable it to proceed. " +msgstr "" + +#: notebook/templates/page.html:121 +msgid "dashboard" +msgstr "" + +#: notebook/templates/page.html:132 +msgid "Logout" +msgstr "" + +#: notebook/templates/page.html:134 +msgid "Login" +msgstr "" + +#: notebook/templates/tree.html:23 +msgid "Files" +msgstr "" + +#: notebook/templates/tree.html:24 +msgid "Running" +msgstr "" + +#: notebook/templates/tree.html:25 +msgid "Clusters" +msgstr "" + +#: notebook/templates/tree.html:32 +msgid "Select items to perform actions on them." +msgstr "" + +#: notebook/templates/tree.html:35 +msgid "Duplicate selected" +msgstr "" + +#: notebook/templates/tree.html:35 +msgid "Duplicate" +msgstr "" + +#: notebook/templates/tree.html:36 +msgid "Rename selected" +msgstr "" + +#: notebook/templates/tree.html:37 +msgid "Move selected" +msgstr "" + +#: notebook/templates/tree.html:37 +msgid "Move" +msgstr "" + +#: notebook/templates/tree.html:38 +msgid "Download selected" +msgstr "" + +#: notebook/templates/tree.html:39 +msgid "Shutdown selected notebook(s)" +msgstr "" + +#: notebook/templates/tree.html:39 +msgid "Shutdown" +msgstr "" + +#: notebook/templates/tree.html:40 +msgid "View selected" +msgstr "" + +#: notebook/templates/tree.html:41 +msgid "Edit selected" +msgstr "" + +#: notebook/templates/tree.html:42 +msgid "Delete selected" +msgstr "" + +#: notebook/templates/tree.html:50 +msgid "Click to browse for a file to upload." +msgstr "" + +#: notebook/templates/tree.html:51 +msgid "Upload" +msgstr "" + +#: notebook/templates/tree.html:65 +msgid "Text File" +msgstr "" + +#: notebook/templates/tree.html:68 +msgid "Folder" +msgstr "" + +#: notebook/templates/tree.html:72 +msgid "Terminal" +msgstr "" + +#: notebook/templates/tree.html:76 +msgid "Terminals Unavailable" +msgstr "" + +#: notebook/templates/tree.html:82 +msgid "Refresh notebook list" +msgstr "" + +#: notebook/templates/tree.html:90 +msgid "Select All / None" +msgstr "" + +#: notebook/templates/tree.html:93 +msgid "Select..." +msgstr "" + +#: notebook/templates/tree.html:98 +msgid "Select All Folders" +msgstr "" + +#: notebook/templates/tree.html:98 +msgid " Folders" +msgstr "" + +#: notebook/templates/tree.html:99 +msgid "Select All Notebooks" +msgstr "" + +#: notebook/templates/tree.html:99 +msgid " All Notebooks" +msgstr "" + +#: notebook/templates/tree.html:100 +msgid "Select Running Notebooks" +msgstr "" + +#: notebook/templates/tree.html:100 +msgid " Running" +msgstr "" + +#: notebook/templates/tree.html:101 +msgid "Select All Files" +msgstr "" + +#: notebook/templates/tree.html:101 +msgid " Files" +msgstr "" + +#: notebook/templates/tree.html:114 +msgid "Last Modified" +msgstr "" + +#: notebook/templates/tree.html:120 +msgid "Name" +msgstr "" + +#: notebook/templates/tree.html:130 +msgid "Currently running Jupyter processes" +msgstr "" + +#: notebook/templates/tree.html:134 +msgid "Refresh running list" +msgstr "" + +#: notebook/templates/tree.html:150 +msgid "There are no terminals running." +msgstr "" + +#: notebook/templates/tree.html:152 +msgid "Terminals are unavailable." +msgstr "" + +#: notebook/templates/tree.html:162 +msgid "Notebooks" +msgstr "" + +#: notebook/templates/tree.html:169 +msgid "There are no notebooks running." +msgstr "" + +#: notebook/templates/tree.html:178 +msgid "Clusters tab is now provided by IPython parallel." +msgstr "" + +#: notebook/templates/tree.html:179 +msgid "See 'IPython parallel' for installation details." +msgstr "" + diff --git a/notebook/i18n/notebook.pot b/notebook/i18n/notebook.pot new file mode 100644 index 000000000..5437b1b0f --- /dev/null +++ b/notebook/i18n/notebook.pot @@ -0,0 +1,480 @@ +# Translations template for Jupyter. +# Copyright (C) 2017 ORGANIZATION +# This file is distributed under the same license as the Jupyter project. +# FIRST AUTHOR , 2017. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Jupyter VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2017-07-08 21:52-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.3.4\n" + +#: notebook/notebookapp.py:53 +msgid "The Jupyter Notebook requires tornado >= 4.0" +msgstr "" + +#: notebook/notebookapp.py:57 +msgid "The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0" +msgstr "" + +#: notebook/notebookapp.py:59 +#, python-format +msgid "The Jupyter Notebook requires tornado >= 4.0, but you have %s" +msgstr "" + +#: notebook/notebookapp.py:209 +msgid "The `ignore_minified_js` flag is deprecated and no longer works." +msgstr "" + +#: notebook/notebookapp.py:210 +#, python-format +msgid "Alternatively use `%s` when working on the notebook's Javascript and LESS" +msgstr "" + +#: notebook/notebookapp.py:211 +msgid "The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0" +msgstr "" + +#: notebook/notebookapp.py:389 +msgid "List currently running notebook servers." +msgstr "" + +#: notebook/notebookapp.py:393 +msgid "Produce machine-readable JSON output." +msgstr "" + +#: notebook/notebookapp.py:397 +msgid "If True, each line of output will be a JSON object with the details from the server info file." +msgstr "" + +#: notebook/notebookapp.py:402 +msgid "Currently running servers:" +msgstr "" + +#: notebook/notebookapp.py:419 +msgid "Don't open the notebook in a browser after startup." +msgstr "" + +#: notebook/notebookapp.py:423 +msgid "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib." +msgstr "" + +#: notebook/notebookapp.py:439 +msgid "Allow the notebook to be run from root user." +msgstr "" + +#: notebook/notebookapp.py:470 +msgid "" +"The Jupyter HTML Notebook.\n" +" \n" +" This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client." +msgstr "" + +#: notebook/notebookapp.py:509 +msgid "Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation" +msgstr "" + +#: notebook/notebookapp.py:540 +msgid "Set the Access-Control-Allow-Credentials: true header" +msgstr "" + +#: notebook/notebookapp.py:544 +msgid "Whether to allow the user to run the notebook as root." +msgstr "" + +#: notebook/notebookapp.py:548 +msgid "The default URL to redirect to from `/`" +msgstr "" + +#: notebook/notebookapp.py:552 +msgid "The IP address the notebook server will listen on." +msgstr "" + +#: notebook/notebookapp.py:565 +#, python-format +msgid "" +"Cannot bind to localhost, using 127.0.0.1 as default ip\n" +"%s" +msgstr "" + +#: notebook/notebookapp.py:579 +msgid "The port the notebook server will listen on." +msgstr "" + +#: notebook/notebookapp.py:583 +msgid "The number of additional ports to try if the specified port is not available." +msgstr "" + +#: notebook/notebookapp.py:587 +msgid "The full path to an SSL/TLS certificate file." +msgstr "" + +#: notebook/notebookapp.py:591 +msgid "The full path to a private key file for usage with SSL/TLS." +msgstr "" + +#: notebook/notebookapp.py:595 +msgid "The full path to a certificate authority certificate for SSL/TLS client authentication." +msgstr "" + +#: notebook/notebookapp.py:599 +msgid "The file where the cookie secret is stored." +msgstr "" + +#: notebook/notebookapp.py:628 +#, python-format +msgid "Writing notebook server cookie secret to %s" +msgstr "" + +#: notebook/notebookapp.py:635 +#, python-format +msgid "Could not set permissions on %s" +msgstr "" + +#: notebook/notebookapp.py:640 +msgid "" +"Token used for authenticating first-time connections to the server.\n" +"\n" +" When no password is enabled,\n" +" the default is to generate a new, random token.\n" +"\n" +" Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.\n" +" " +msgstr "" + +#: notebook/notebookapp.py:650 +msgid "" +"One-time token used for opening a browser.\n" +" Once used, this token cannot be used again.\n" +" " +msgstr "" + +#: notebook/notebookapp.py:726 +msgid "" +"Specify Where to open the notebook on startup. This is the\n" +" `new` argument passed to the standard library method `webbrowser.open`.\n" +" The behaviour is not guaranteed, but depends on browser support. Valid\n" +" values are:\n" +" 2 opens a new tab,\n" +" 1 opens a new window,\n" +" 0 opens in an existing window.\n" +" See the `webbrowser.open` documentation for details.\n" +" " +msgstr "" + +#: notebook/notebookapp.py:737 +msgid "DEPRECATED, use tornado_settings" +msgstr "" + +#: notebook/notebookapp.py:742 +msgid "" +"\n" +" webapp_settings is deprecated, use tornado_settings.\n" +msgstr "" + +#: notebook/notebookapp.py:746 +msgid "Supply overrides for the tornado.web.Application that the Jupyter notebook uses." +msgstr "" + +#: notebook/notebookapp.py:750 +msgid "" +"\n" +" Set the tornado compression options for websocket connections.\n" +"\n" +" This value will be returned from :meth:`WebSocketHandler.get_compression_options`.\n" +" None (default) will disable compression.\n" +" A dict (even an empty one) will enable compression.\n" +"\n" +" See the tornado docs for WebSocketHandler.get_compression_options for details.\n" +" " +msgstr "" + +#: notebook/notebookapp.py:761 +msgid "Supply overrides for terminado. Currently only supports \"shell_command\"." +msgstr "" + +#: notebook/notebookapp.py:764 +msgid "Extra keyword arguments to pass to `set_secure_cookie`. See tornado's set_secure_cookie docs for details." +msgstr "" + +#: notebook/notebookapp.py:768 +msgid "" +"Supply SSL options for the tornado HTTPServer.\n" +" See the tornado docs for details." +msgstr "" + +#: notebook/notebookapp.py:772 +msgid "Supply extra arguments that will be passed to Jinja environment." +msgstr "" + +#: notebook/notebookapp.py:776 +msgid "Extra variables to supply to jinja templates when rendering." +msgstr "" + +#: notebook/notebookapp.py:812 +msgid "DEPRECATED use base_url" +msgstr "" + +#: notebook/notebookapp.py:816 +msgid "base_project_url is deprecated, use base_url" +msgstr "" + +#: notebook/notebookapp.py:832 +msgid "Path to search for custom.js, css" +msgstr "" + +#: notebook/notebookapp.py:844 +msgid "" +"Extra paths to search for serving jinja templates.\n" +"\n" +" Can be used to override templates from notebook.templates." +msgstr "" + +#: notebook/notebookapp.py:855 +msgid "extra paths to look for Javascript notebook extensions" +msgstr "" + +#: notebook/notebookapp.py:900 +#, python-format +msgid "Using MathJax: %s" +msgstr "" + +#: notebook/notebookapp.py:903 +msgid "The MathJax.js configuration file that is to be used." +msgstr "" + +#: notebook/notebookapp.py:908 +#, python-format +msgid "Using MathJax configuration file: %s" +msgstr "" + +#: notebook/notebookapp.py:914 +msgid "The notebook manager class to use." +msgstr "" + +#: notebook/notebookapp.py:920 +msgid "The kernel manager class to use." +msgstr "" + +#: notebook/notebookapp.py:926 +msgid "The session manager class to use." +msgstr "" + +#: notebook/notebookapp.py:932 +msgid "The config manager class to use" +msgstr "" + +#: notebook/notebookapp.py:953 +msgid "The login handler class to use." +msgstr "" + +#: notebook/notebookapp.py:960 +msgid "The logout handler class to use." +msgstr "" + +#: notebook/notebookapp.py:964 +msgid "Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headerssent by the upstream reverse proxy. Necessary if the proxy handles SSL" +msgstr "" + +#: notebook/notebookapp.py:976 +msgid "" +"\n" +" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.\n" +" " +msgstr "" + +#: notebook/notebookapp.py:988 +msgid "Support for specifying --pylab on the command line has been removed." +msgstr "" + +#: notebook/notebookapp.py:990 +msgid "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself." +msgstr "" + +#: notebook/notebookapp.py:995 +msgid "The directory to use for notebooks and kernels." +msgstr "" + +#: notebook/notebookapp.py:1018 +#, python-format +msgid "No such notebook dir: '%r'" +msgstr "" + +#: notebook/notebookapp.py:1031 +msgid "DEPRECATED use the nbserver_extensions dict instead" +msgstr "" + +#: notebook/notebookapp.py:1036 +msgid "server_extensions is deprecated, use nbserver_extensions" +msgstr "" + +#: notebook/notebookapp.py:1040 +msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order." +msgstr "" + +#: notebook/notebookapp.py:1049 +msgid "Reraise exceptions encountered loading server extensions?" +msgstr "" + +#: notebook/notebookapp.py:1052 +msgid "" +"(msgs/sec)\n" +" Maximum rate at which messages can be sent on iopub before they are\n" +" limited." +msgstr "" + +#: notebook/notebookapp.py:1056 +msgid "" +"(bytes/sec)\n" +" Maximum rate at which stream output can be sent on iopub before they are\n" +" limited." +msgstr "" + +#: notebook/notebookapp.py:1060 +msgid "" +"(sec) Time window used to \n" +" check the message and data rate limits." +msgstr "" + +#: notebook/notebookapp.py:1071 +#, python-format +msgid "No such file or directory: %s" +msgstr "" + +#: notebook/notebookapp.py:1141 +msgid "Notebook servers are configured to only be run with a password." +msgstr "" + +#: notebook/notebookapp.py:1142 +msgid "Hint: run the following command to set a password" +msgstr "" + +#: notebook/notebookapp.py:1143 +msgid "\t$ python -m notebook.auth password" +msgstr "" + +#: notebook/notebookapp.py:1181 +#, python-format +msgid "The port %i is already in use, trying another port." +msgstr "" + +#: notebook/notebookapp.py:1184 +#, python-format +msgid "Permission to listen on port %i denied" +msgstr "" + +#: notebook/notebookapp.py:1193 +msgid "ERROR: the notebook server could not be started because no available port could be found." +msgstr "" + +#: notebook/notebookapp.py:1199 +msgid "[all ip addresses on your system]" +msgstr "" + +#: notebook/notebookapp.py:1223 +#, python-format +msgid "Terminals not available (error was %s)" +msgstr "" + +#: notebook/notebookapp.py:1259 +msgid "interrupted" +msgstr "" + +#: notebook/notebookapp.py:1261 +msgid "y" +msgstr "" + +#: notebook/notebookapp.py:1262 +msgid "n" +msgstr "" + +#: notebook/notebookapp.py:1263 +#, python-format +msgid "Shutdown this notebook server (%s/[%s])? " +msgstr "" + +#: notebook/notebookapp.py:1269 +msgid "Shutdown confirmed" +msgstr "" + +#: notebook/notebookapp.py:1273 +msgid "No answer for 5s:" +msgstr "" + +#: notebook/notebookapp.py:1274 +msgid "resuming operation..." +msgstr "" + +#: notebook/notebookapp.py:1282 +#, python-format +msgid "received signal %s, stopping" +msgstr "" + +#: notebook/notebookapp.py:1338 +#, python-format +msgid "Error loading server extension %s" +msgstr "" + +#: notebook/notebookapp.py:1369 +#, python-format +msgid "Shutting down %d kernels" +msgstr "" + +#: notebook/notebookapp.py:1375 +#, python-format +msgid "%d active kernel" +msgid_plural "%d active kernels" +msgstr[0] "" +msgstr[1] "" + +#: notebook/notebookapp.py:1379 +#, python-format +msgid "" +"The Jupyter Notebook is running at:\n" +"\r" +"%s" +msgstr "" + +#: notebook/notebookapp.py:1426 +msgid "Running as root is not recommended. Use --allow-root to bypass." +msgstr "" + +#: notebook/notebookapp.py:1432 +msgid "Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)." +msgstr "" + +#: notebook/notebookapp.py:1434 +msgid "Welcome to Project Jupyter! Explore the various tools available and their corresponding documentation. If you are interested in contributing to the platform, please visit the communityresources section at http://jupyter.org/community.html." +msgstr "" + +#: notebook/notebookapp.py:1445 +#, python-format +msgid "No web browser found: %s." +msgstr "" + +#: notebook/notebookapp.py:1450 +#, python-format +msgid "%s does not exist" +msgstr "" + +#: notebook/notebookapp.py:1484 +msgid "Interrupted..." +msgstr "" + +#: notebook/services/contents/filemanager.py:506 +#, python-format +msgid "Serving notebooks from local directory: %s" +msgstr "" + +#: notebook/services/contents/manager.py:68 +msgid "Untitled" +msgstr "" + diff --git a/notebook/jstest.py b/notebook/jstest.py index 684ebf19d..0e531f64f 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -324,8 +324,6 @@ class JSController(TestController): c.start() env = os.environ.copy() env.update(self.env) - if self.engine == 'phantomjs': - env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1' self.server = subprocess.Popen(command, stdout = c.writefd, stderr = subprocess.STDOUT, diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 498405a6d..132aa4ddf 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -10,6 +10,7 @@ import notebook import binascii import datetime import errno +import gettext import importlib import io import json @@ -34,23 +35,28 @@ except ImportError: #PY2 from jinja2 import Environment, FileSystemLoader +# Set up message catalog access +base_dir = os.path.realpath(os.path.join(__file__, '..', '..')) +trans = gettext.translation('notebook', localedir=os.path.join(base_dir, 'notebook/i18n'), fallback=True) +trans.install() +_ = trans.gettext + # Install the pyzmq ioloop. This has to be done before anything else from # tornado is imported. from zmq.eventloop import ioloop ioloop.install() # check for tornado 3.1.0 -msg = "The Jupyter Notebook requires tornado >= 4.0" try: import tornado except ImportError: - raise ImportError(msg) + raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0")) try: version_info = tornado.version_info except AttributeError: - raise ImportError(msg + ", but you have < 1.1.0") + raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0")) if version_info < (4,0): - raise ImportError(msg + ", but you have %s" % tornado.version) + raise ImportError(_("The Jupyter Notebook requires tornado >= 4.0, but you have %s") % tornado.version) from tornado import httpserver from tornado import web @@ -75,6 +81,7 @@ from .services.kernels.kernelmanager import MappingKernelManager from .services.config import ConfigManager from .services.contents.manager import ContentsManager from .services.contents.filemanager import FileContentsManager +from .services.contents.largefilemanager import LargeFileManager from .services.sessions.sessionmanager import SessionManager from .auth.login import LoginHandler @@ -92,7 +99,7 @@ from jupyter_client.kernelspec import KernelSpecManager, NoSuchKernel, NATIVE_KE from jupyter_client.session import Session from nbformat.sign import NotebookNotary from traitlets import ( - Dict, Unicode, Integer, List, Bool, Bytes, Instance, + Any, Dict, Unicode, Integer, List, Bool, Bytes, Instance, TraitError, Type, Float, observe, default, validate ) from ipython_genutils import py3compat @@ -112,15 +119,6 @@ jupyter notebook --certfile=mycert.pem # use SSL/TLS certificate jupyter notebook password # enter a password to protect the server """ -DEV_NOTE_NPM = """It looks like you're running the notebook from source. -If you're working on the Javascript of the notebook, try running - - npm run build:watch - -in another terminal window to have the system incrementally -watch and build the notebook's JavaScript for you, as you make changes. -""" - #----------------------------------------------------------------------------- # Helper functions #----------------------------------------------------------------------------- @@ -153,17 +151,11 @@ class NotebookWebApplication(web.Application): config_manager, log, base_url, default_url, settings_overrides, jinja_env_options): - # If the user is running the notebook in a git directory, make the assumption - # that this is a dev install and suggest to the developer `npm run build:watch`. - base_dir = os.path.realpath(os.path.join(__file__, '..', '..')) - dev_mode = os.path.exists(os.path.join(base_dir, '.git')) - if dev_mode: - log.info(DEV_NOTE_NPM) settings = self.init_settings( jupyter_app, kernel_manager, contents_manager, - session_manager, kernel_spec_manager, config_manager, log, base_url, - default_url, settings_overrides, jinja_env_options) + session_manager, kernel_spec_manager, config_manager, log, + base_url, default_url, settings_overrides, jinja_env_options) handlers = self.init_handlers(settings) super(NotebookWebApplication, self).__init__(handlers, **settings) @@ -185,9 +177,27 @@ class NotebookWebApplication(web.Application): jenv_opt = {"autoescape": True} jenv_opt.update(jinja_env_options if jinja_env_options else {}) - env = Environment(loader=FileSystemLoader(template_path), **jenv_opt) - + env = Environment(loader=FileSystemLoader(template_path), extensions=['jinja2.ext.i18n'], **jenv_opt) sys_info = get_sys_info() + + # If the user is running the notebook in a git directory, make the assumption + # that this is a dev install and suggest to the developer `npm run build:watch`. + base_dir = os.path.realpath(os.path.join(__file__, '..', '..')) + dev_mode = os.path.exists(os.path.join(base_dir, '.git')) + + nbui = gettext.translation('nbui', localedir=os.path.join(base_dir, 'notebook/i18n'), fallback=True) + env.install_gettext_translations(nbui, newstyle=False) + + if dev_mode: + DEV_NOTE_NPM = """It looks like you're running the notebook from source. + If you're working on the Javascript of the notebook, try running + + %s + + in another terminal window to have the system incrementally + watch and build the notebook's JavaScript for you, as you make changes.""" % 'npm run build:watch' + log.info(DEV_NOTE_NPM) + if sys_info['commit_source'] == 'repository': # don't cache (rely on 304) when working from master version_hash = '' @@ -196,12 +206,17 @@ class NotebookWebApplication(web.Application): version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S") if jupyter_app.ignore_minified_js: - log.warning("""The `ignore_minified_js` flag is deprecated and no - longer works. Alternatively use `npm run build:watch` when - working on the notebook's Javascript and LESS""") - warnings.warn("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0", DeprecationWarning) + log.warning(_("""The `ignore_minified_js` flag is deprecated and no longer works.""")) + log.warning(_("""Alternatively use `%s` when working on the notebook's Javascript and LESS""") % 'npm run build:watch') + warnings.warn(_("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"), DeprecationWarning) now = utcnow() + + root_dir = contents_manager.root_dir + home = os.path.expanduser('~') + if root_dir.startswith(home + os.path.sep): + # collapse $HOME to ~ + root_dir = '~' + root_dir[len(home):] settings = dict( # basics @@ -255,6 +270,7 @@ class NotebookWebApplication(web.Application): mathjax_config=jupyter_app.mathjax_config, config=jupyter_app.config, config_dir=jupyter_app.config_dir, + server_root_dir=root_dir, jinja2_env=env, terminals_available=False, # Set later if terminals are available ) @@ -286,6 +302,7 @@ class NotebookWebApplication(web.Application): handlers.extend(load_handlers('services.nbconvert.handlers')) handlers.extend(load_handlers('services.kernelspecs.handlers')) handlers.extend(load_handlers('services.security.handlers')) + handlers.extend(load_handlers('services.shutdown')) handlers.append( (r"/nbextensions/(.*)", FileFindHandler, { @@ -338,22 +355,51 @@ class NotebookPasswordApp(JupyterApp): self.log.info("Wrote hashed password to %s" % self.config_file) +class NbserverStopApp(JupyterApp): + version = __version__ + description="Stop currently running notebook server for a given port" + + port = Integer(8888, config=True, + help="Port of the server to be killed. Default 8888") + + def parse_command_line(self, argv=None): + super(NbserverStopApp, self).parse_command_line(argv) + if self.extra_args: + self.port=int(self.extra_args[0]) + + def start(self): + servers = list(list_running_servers(self.runtime_dir)) + if not servers: + self.exit("There are no running servers") + for server in servers: + if server['port'] == self.port: + self.log.debug("Shutting down notebook server with PID: %i", server['pid']) + os.kill(server['pid'], signal.SIGTERM) + return + else: + print("There is currently no server running on port {}".format(self.port), file=sys.stderr) + print("Ports currently in use:", file=sys.stderr) + for server in servers: + print(" - {}".format(server['port']), file=sys.stderr) + self.exit(1) + + class NbserverListApp(JupyterApp): version = __version__ - description="List currently running notebook servers." + description=_("List currently running notebook servers.") flags = dict( json=({'NbserverListApp': {'json': True}}, - "Produce machine-readable JSON output."), + _("Produce machine-readable JSON output.")), ) json = Bool(False, config=True, - help="If True, each line of output will be a JSON object with the " - "details from the server info file.") + help=_("If True, each line of output will be a JSON object with the " + "details from the server info file.")) def start(self): if not self.json: - print("Currently running servers:") + print(_("Currently running servers:")) for serverinfo in list_running_servers(self.runtime_dir): if self.json: print(json.dumps(serverinfo)) @@ -370,11 +416,11 @@ class NbserverListApp(JupyterApp): flags = dict(base_flags) flags['no-browser']=( {'NotebookApp' : {'open_browser' : False}}, - "Don't open the notebook in a browser after startup." + _("Don't open the notebook in a browser after startup.") ) flags['pylab']=( {'NotebookApp' : {'pylab' : 'warn'}}, - "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib." + _("DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.") ) flags['no-mathjax']=( {'NotebookApp' : {'enable_mathjax' : False}}, @@ -390,7 +436,7 @@ flags['no-mathjax']=( flags['allow-root']=( {'NotebookApp' : {'allow_root' : True}}, - "Allow the notebook to be run from root user." + _("Allow the notebook to be run from root user.") ) # Add notebook manager flags @@ -421,12 +467,9 @@ class NotebookApp(JupyterApp): name = 'jupyter-notebook' version = __version__ - description = """ - The Jupyter HTML Notebook. - - This launches a Tornado based HTML Notebook Server that serves up an - HTML5/Javascript Notebook client. - """ + description = _("""The Jupyter HTML Notebook. + + This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client.""") examples = _examples aliases = aliases flags = flags @@ -441,6 +484,7 @@ class NotebookApp(JupyterApp): subcommands = dict( list=(NbserverListApp, NbserverListApp.description.splitlines()[0]), + stop=(NbserverStopApp, NbserverStopApp.description.splitlines()[0]), password=(NotebookPasswordApp, NotebookPasswordApp.description.splitlines()[0]), ) @@ -462,7 +506,7 @@ class NotebookApp(JupyterApp): ignore_minified_js = Bool(False, config=True, - help='Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation', + help=_('Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation'), ) # file to be opened in the notebook server @@ -493,19 +537,19 @@ class NotebookApp(JupyterApp): ) allow_credentials = Bool(False, config=True, - help="Set the Access-Control-Allow-Credentials: true header" + help=_("Set the Access-Control-Allow-Credentials: true header") ) allow_root = Bool(False, config=True, - help="Whether to allow the user to run the notebook as root." + help=_("Whether to allow the user to run the notebook as root.") ) default_url = Unicode('/tree', config=True, - help="The default URL to redirect to from `/`" + help=_("The default URL to redirect to from `/`") ) ip = Unicode('localhost', config=True, - help="The IP address the notebook server will listen on." + help=_("The IP address the notebook server will listen on.") ) @default('ip') @@ -518,7 +562,7 @@ class NotebookApp(JupyterApp): try: s.bind(('localhost', 0)) except socket.error as e: - self.log.warning("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e) + self.log.warning(_("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s"), e) return '127.0.0.1' else: s.close() @@ -532,27 +576,27 @@ class NotebookApp(JupyterApp): return value port = Integer(8888, config=True, - help="The port the notebook server will listen on." + help=_("The port the notebook server will listen on.") ) port_retries = Integer(50, config=True, - help="The number of additional ports to try if the specified port is not available." + help=_("The number of additional ports to try if the specified port is not available.") ) certfile = Unicode(u'', config=True, - help="""The full path to an SSL/TLS certificate file.""" + help=_("""The full path to an SSL/TLS certificate file.""") ) keyfile = Unicode(u'', config=True, - help="""The full path to a private key file for usage with SSL/TLS.""" + help=_("""The full path to a private key file for usage with SSL/TLS.""") ) client_ca = Unicode(u'', config=True, - help="""The full path to a certificate authority certificate for SSL/TLS client authentication.""" + help=_("""The full path to a certificate authority certificate for SSL/TLS client authentication.""") ) cookie_secret_file = Unicode(config=True, - help="""The file where the cookie secret is stored.""" + help=_("""The file where the cookie secret is stored.""") ) @default('cookie_secret_file') @@ -581,32 +625,31 @@ class NotebookApp(JupyterApp): def _write_cookie_secret_file(self, secret): """write my secret to my secret_file""" - self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file) + self.log.info(_("Writing notebook server cookie secret to %s"), self.cookie_secret_file) with io.open(self.cookie_secret_file, 'wb') as f: f.write(secret) try: os.chmod(self.cookie_secret_file, 0o600) except OSError: self.log.warning( - "Could not set permissions on %s", + _("Could not set permissions on %s"), self.cookie_secret_file ) token = Unicode('', - help="""Token used for authenticating first-time connections to the server. + help=_("""Token used for authenticating first-time connections to the server. When no password is enabled, the default is to generate a new, random token. Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED. - """ + """) ).tag(config=True) one_time_token = Unicode( - help="""One-time token used for opening a browser. - + help=_("""One-time token used for opening a browser. Once used, this token cannot be used again. - """ + """) ) _token_generated = True @@ -639,7 +682,7 @@ class NotebookApp(JupyterApp): password_required = Bool(False, config=True, help="""Forces users to use a password for the Notebook server. This is useful in a multi user environment, for instance when - everybody in the LAN can access each other's machine though ssh. + everybody in the LAN can access each other's machine through ssh. In such a case, server the notebook server on localhost is not secure since any user can connect to the notebook server via ssh. @@ -678,37 +721,59 @@ class NotebookApp(JupyterApp): standard library module, which allows setting of the BROWSER environment variable to override it. """) - + + webbrowser_open_new = Integer(2, config=True, + help=_("""Specify Where to open the notebook on startup. This is the + `new` argument passed to the standard library method `webbrowser.open`. + The behaviour is not guaranteed, but depends on browser support. Valid + values are: + 2 opens a new tab, + 1 opens a new window, + 0 opens in an existing window. + See the `webbrowser.open` documentation for details. + """)) + webapp_settings = Dict(config=True, - help="DEPRECATED, use tornado_settings" + help=_("DEPRECATED, use tornado_settings") ) @observe('webapp_settings') def _update_webapp_settings(self, change): - self.log.warning("\n webapp_settings is deprecated, use tornado_settings.\n") + self.log.warning(_("\n webapp_settings is deprecated, use tornado_settings.\n")) self.tornado_settings = change['new'] tornado_settings = Dict(config=True, - help="Supply overrides for the tornado.web.Application that the " - "Jupyter notebook uses.") - + help=_("Supply overrides for the tornado.web.Application that the " + "Jupyter notebook uses.")) + + websocket_compression_options = Any(None, config=True, + help=_(""" + Set the tornado compression options for websocket connections. + + This value will be returned from :meth:`WebSocketHandler.get_compression_options`. + None (default) will disable compression. + A dict (even an empty one) will enable compression. + + See the tornado docs for WebSocketHandler.get_compression_options for details. + """) + ) terminado_settings = Dict(config=True, - help='Supply overrides for terminado. Currently only supports "shell_command".') + help=_('Supply overrides for terminado. Currently only supports "shell_command".')) cookie_options = Dict(config=True, - help="Extra keyword arguments to pass to `set_secure_cookie`." - " See tornado's set_secure_cookie docs for details." + help=_("Extra keyword arguments to pass to `set_secure_cookie`." + " See tornado's set_secure_cookie docs for details.") ) ssl_options = Dict(config=True, - help="""Supply SSL options for the tornado HTTPServer. - See the tornado docs for details.""") + help=_("""Supply SSL options for the tornado HTTPServer. + See the tornado docs for details.""")) jinja_environment_options = Dict(config=True, - help="Supply extra arguments that will be passed to Jinja environment.") + help=_("Supply extra arguments that will be passed to Jinja environment.")) jinja_template_vars = Dict( config=True, - help="Extra variables to supply to jinja templates when rendering.", + help=_("Extra variables to supply to jinja templates when rendering."), ) enable_mathjax = Bool(True, config=True, @@ -740,15 +805,15 @@ class NotebookApp(JupyterApp): value = proposal['value'] if not value.startswith('/'): value = '/' + value - elif not value.endswith('/'): + if not value.endswith('/'): value = value + '/' return value - base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""") + base_project_url = Unicode('/', config=True, help=_("""DEPRECATED use base_url""")) @observe('base_project_url') def _update_base_project_url(self, change): - self.log.warning("base_project_url is deprecated, use base_url") + self.log.warning(_("base_project_url is deprecated, use base_url")) self.base_url = change['new'] extra_static_paths = List(Unicode(), config=True, @@ -764,7 +829,7 @@ class NotebookApp(JupyterApp): return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH] static_custom_path = List(Unicode(), - help="""Path to search for custom.js, css""" + help=_("""Path to search for custom.js, css""") ) @default('static_custom_path') @@ -776,9 +841,9 @@ class NotebookApp(JupyterApp): ] extra_template_paths = List(Unicode(), config=True, - help="""Extra paths to search for serving jinja templates. + help=_("""Extra paths to search for serving jinja templates. - Can be used to override templates from notebook.templates.""" + Can be used to override templates from notebook.templates.""") ) @property @@ -787,7 +852,7 @@ class NotebookApp(JupyterApp): return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST extra_nbextensions_path = List(Unicode(), config=True, - help="""extra paths to look for Javascript notebook extensions""" + help=_("""extra paths to look for Javascript notebook extensions""") ) @property @@ -832,39 +897,39 @@ class NotebookApp(JupyterApp): # enable_mathjax=False overrides mathjax_url self.mathjax_url = u'' else: - self.log.info("Using MathJax: %s", new) + self.log.info(_("Using MathJax: %s"), new) mathjax_config = Unicode("TeX-AMS-MML_HTMLorMML-full,Safe", config=True, - help="""The MathJax.js configuration file that is to be used.""" + help=_("""The MathJax.js configuration file that is to be used.""") ) @observe('mathjax_config') def _update_mathjax_config(self, change): - self.log.info("Using MathJax configuration file: %s", change['new']) + self.log.info(_("Using MathJax configuration file: %s"), change['new']) contents_manager_class = Type( - default_value=FileContentsManager, + default_value=LargeFileManager, klass=ContentsManager, config=True, - help='The notebook manager class to use.' + help=_('The notebook manager class to use.') ) kernel_manager_class = Type( default_value=MappingKernelManager, config=True, - help='The kernel manager class to use.' + help=_('The kernel manager class to use.') ) session_manager_class = Type( default_value=SessionManager, config=True, - help='The session manager class to use.' + help=_('The session manager class to use.') ) config_manager_class = Type( default_value=ConfigManager, config = True, - help='The config manager class to use' + help=_('The config manager class to use') ) kernel_spec_manager = Instance(KernelSpecManager, allow_none=True) @@ -885,19 +950,19 @@ class NotebookApp(JupyterApp): default_value=LoginHandler, klass=web.RequestHandler, config=True, - help='The login handler class to use.', + help=_('The login handler class to use.'), ) logout_handler_class = Type( default_value=LogoutHandler, klass=web.RequestHandler, config=True, - help='The logout handler class to use.', + help=_('The logout handler class to use.'), ) trust_xheaders = Bool(False, config=True, - help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" - "sent by the upstream reverse proxy. Necessary if the proxy handles SSL") + help=(_("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" + "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")) ) info_file = Unicode() @@ -908,9 +973,9 @@ class NotebookApp(JupyterApp): return os.path.join(self.runtime_dir, info_file) pylab = Unicode('disabled', config=True, - help=""" + help=_(""" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. - """ + """) ) @observe('pylab') @@ -920,14 +985,14 @@ class NotebookApp(JupyterApp): backend = ' %s' % change['new'] else: backend = '' - self.log.error("Support for specifying --pylab on the command line has been removed.") + self.log.error(_("Support for specifying --pylab on the command line has been removed.")) self.log.error( - "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend) + _("Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.").format(backend) ) self.exit(1) notebook_dir = Unicode(config=True, - help="The directory to use for notebooks and kernels." + help=_("The directory to use for notebooks and kernels.") ) @default('notebook_dir') @@ -950,7 +1015,7 @@ class NotebookApp(JupyterApp): # If we receive a non-absolute path, make it absolute. value = os.path.abspath(value) if not os.path.isdir(value): - raise TraitError("No such notebook dir: %r" % value) + raise TraitError(trans.gettext("No such notebook dir: '%r'") % value) return value @observe('notebook_dir') @@ -963,37 +1028,37 @@ class NotebookApp(JupyterApp): # TODO: Remove me in notebook 5.0 server_extensions = List(Unicode(), config=True, - help=("DEPRECATED use the nbserver_extensions dict instead") + help=(_("DEPRECATED use the nbserver_extensions dict instead")) ) @observe('server_extensions') def _update_server_extensions(self, change): - self.log.warning("server_extensions is deprecated, use nbserver_extensions") + self.log.warning(_("server_extensions is deprecated, use nbserver_extensions")) self.server_extensions = change['new'] nbserver_extensions = Dict({}, config=True, - help=("Dict of Python modules to load as notebook server extensions." + help=(_("Dict of Python modules to load as notebook server extensions." "Entry values can be used to enable and disable the loading of" "the extensions. The extensions will be loaded in alphabetical " - "order.") + "order.")) ) reraise_server_extension_failures = Bool( False, config=True, - help="Reraise exceptions encountered loading server extensions?", + help=_("Reraise exceptions encountered loading server extensions?"), ) - iopub_msg_rate_limit = Float(1000, config=True, help="""(msgs/sec) + iopub_msg_rate_limit = Float(1000, config=True, help=_("""(msgs/sec) Maximum rate at which messages can be sent on iopub before they are - limited.""") + limited.""")) - iopub_data_rate_limit = Float(1000000, config=True, help="""(bytes/sec) - Maximum rate at which messages can be sent on iopub before they are - limited.""") + iopub_data_rate_limit = Float(1000000, config=True, help=_("""(bytes/sec) + Maximum rate at which stream output can be sent on iopub before they are + limited.""")) - rate_limit_window = Float(3, config=True, help="""(sec) Time window used to - check the message and data rate limits.""") + rate_limit_window = Float(3, config=True, help=_("""(sec) Time window used to + check the message and data rate limits.""")) def parse_command_line(self, argv=None): super(NotebookApp, self).parse_command_line(argv) @@ -1003,7 +1068,7 @@ class NotebookApp(JupyterApp): f = os.path.abspath(arg0) self.argv.remove(arg0) if not os.path.exists(f): - self.log.critical("No such file or directory: %s", f) + self.log.critical(_("No such file or directory: %s"), f) self.exit(1) # Use config here, to ensure that it takes higher priority than @@ -1038,7 +1103,6 @@ class NotebookApp(JupyterApp): self.config_manager = self.config_manager_class( parent=self, log=self.log, - config_dir=os.path.join(self.config_dir, 'nbconfig'), ) def init_logging(self): @@ -1059,6 +1123,7 @@ class NotebookApp(JupyterApp): def init_webapp(self): """initialize tornado webapp and httpserver""" self.tornado_settings['allow_origin'] = self.allow_origin + self.tornado_settings['websocket_compression_options'] = self.websocket_compression_options if self.allow_origin_pat: self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) self.tornado_settings['allow_credentials'] = self.allow_credentials @@ -1073,9 +1138,9 @@ class NotebookApp(JupyterApp): self.default_url = url_path_join(self.base_url, self.default_url) if self.password_required and (not self.password): - self.log.critical("Notebook servers are configured to only be run with a password.") - self.log.critical("Hint: run the following command to set a password") - self.log.critical("\t$ python -m notebook.auth password") + self.log.critical(_("Notebook servers are configured to only be run with a password.")) + self.log.critical(_("Hint: run the following command to set a password")) + self.log.critical(_("\t$ python -m notebook.auth password")) sys.exit(1) self.web_app = NotebookWebApplication( @@ -1113,10 +1178,10 @@ class NotebookApp(JupyterApp): self.http_server.listen(port, self.ip) except socket.error as e: if e.errno == errno.EADDRINUSE: - self.log.info('The port %i is already in use, trying another port.' % port) + self.log.info(_('The port %i is already in use, trying another port.') % port) continue elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)): - self.log.warning("Permission to listen on port %i denied" % port) + self.log.warning(_("Permission to listen on port %i denied") % port) continue else: raise @@ -1125,13 +1190,13 @@ class NotebookApp(JupyterApp): success = True break if not success: - self.log.critical('ERROR: the notebook server could not be started because ' - 'no available port could be found.') + self.log.critical(_('ERROR: the notebook server could not be started because ' + 'no available port could be found.')) self.exit(1) @property def display_url(self): - ip = self.ip if self.ip else '[all ip addresses on your system]' + ip = self.ip if self.ip else _('[all ip addresses on your system]') url = self._url(ip) if self.token: # Don't log full token if it came from config @@ -1155,10 +1220,10 @@ class NotebookApp(JupyterApp): self.web_app.settings['terminals_available'] = True except ImportError as e: log = self.log.debug if sys.platform == 'win32' else self.log.warning - log("Terminals not available (error was %s)", e) + log(_("Terminals not available (error was %s)"), e) def init_signal(self): - if not sys.platform.startswith('win') and sys.stdin.isatty(): + if not sys.platform.startswith('win') and sys.stdin and sys.stdin.isatty(): signal.signal(signal.SIGINT, self._handle_sigint) signal.signal(signal.SIGTERM, self._signal_stop) if hasattr(signal, 'SIGUSR1'): @@ -1191,20 +1256,22 @@ class NotebookApp(JupyterApp): This doesn't work on Windows. """ info = self.log.info - info('interrupted') + info(_('interrupted')) print(self.notebook_info()) - sys.stdout.write("Shutdown this notebook server (y/[n])? ") + yes = _('y') + no = _('n') + sys.stdout.write(_("Shutdown this notebook server (%s/[%s])? ") % (yes, no)) sys.stdout.flush() r,w,x = select.select([sys.stdin], [], [], 5) if r: line = sys.stdin.readline() - if line.lower().startswith('y') and 'n' not in line.lower(): - self.log.critical("Shutdown confirmed") + if line.lower().startswith(yes) and no not in line.lower(): + self.log.critical(_("Shutdown confirmed")) ioloop.IOLoop.current().stop() return else: - print("No answer for 5s:", end=' ') - print("resuming operation...") + print(_("No answer for 5s:"), end=' ') + print(_("resuming operation...")) # no answer, or answer is no: # set it back to original SIGINT handler # use IOLoop.add_callback because signal.signal must be called @@ -1212,7 +1279,7 @@ class NotebookApp(JupyterApp): ioloop.IOLoop.current().add_callback(self._restore_sigint_handler) def _signal_stop(self, sig, frame): - self.log.critical("received signal %s, stopping", sig) + self.log.critical(_("received signal %s, stopping"), sig) ioloop.IOLoop.current().stop() def _signal_info(self, sig, frame): @@ -1268,7 +1335,7 @@ class NotebookApp(JupyterApp): except Exception: if self.reraise_server_extension_failures: raise - self.log.warning("Error loading server extension %s", modulename, + self.log.warning(_("Error loading server extension %s"), modulename, exc_info=True) def init_mime_overrides(self): @@ -1298,14 +1365,21 @@ class NotebookApp(JupyterApp): The kernels will shutdown themselves when this process no longer exists, but explicit shutdown allows the KernelManagers to cleanup the connection files. """ - self.log.info('Shutting down kernels') + n_kernels = len(self.kernel_manager.list_kernel_ids()) + kernel_msg = trans.ngettext('Shutting down %d kernel', 'Shutting down %d kernels', n_kernels) + self.log.info(kernel_msg % n_kernels) self.kernel_manager.shutdown_all() def notebook_info(self): "Return the current working directory and the server url information" info = self.contents_manager.info_string() + "\n" - info += "%d active kernels \n" % len(self.kernel_manager._kernels) - return info + "The Jupyter Notebook is running at: %s" % self.display_url + n_kernels = len(self.kernel_manager.list_kernel_ids()) + kernel_msg = trans.ngettext("%d active kernel", "%d active kernels", n_kernels) + info += kernel_msg % n_kernels + info += "\n" + # Format the info so that the URL fits on a single line in 80 char display + info += _("The Jupyter Notebook is running at:\n%s") % self.display_url + return info def server_info(self): """Return a JSONable dict of information about this server.""" @@ -1342,6 +1416,8 @@ class NotebookApp(JupyterApp): This method takes no arguments so all configuration and initialization must be done prior to calling this method.""" + super(NotebookApp, self).start() + if not self.allow_root: # check if we are running as root, and abort if it's not allowed try: @@ -1349,20 +1425,18 @@ class NotebookApp(JupyterApp): except AttributeError: uid = -1 # anything nonzero here, since we can't check UID assume non-root if uid == 0: - self.log.critical("Running as root is not recommended. Use --allow-root to bypass.") + self.log.critical(_("Running as root is not recommended. Use --allow-root to bypass.")) self.exit(1) - super(NotebookApp, self).start() - info = self.log.info for line in self.notebook_info().split("\n"): info(line) - info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).") + info(_("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")) if 'dev' in notebook.__version__: - info("Welcome to Project Jupyter! Explore the various tools available" + info(_("Welcome to Project Jupyter! Explore the various tools available" " and their corresponding documentation. If you are interested" " in contributing to the platform, please visit the community" - "resources section at http://jupyter.org/community.html.") + "resources section at http://jupyter.org/community.html.")) self.write_server_info_file() @@ -1370,12 +1444,12 @@ class NotebookApp(JupyterApp): try: browser = webbrowser.get(self.browser or None) except webbrowser.Error as e: - self.log.warning('No web browser found: %s.' % e) + self.log.warning(_('No web browser found: %s.') % e) browser = None if self.file_to_run: if not os.path.exists(self.file_to_run): - self.log.critical("%s does not exist" % self.file_to_run) + self.log.critical(_("%s does not exist") % self.file_to_run) self.exit(1) relpath = os.path.relpath(self.file_to_run, self.notebook_dir) @@ -1387,7 +1461,7 @@ class NotebookApp(JupyterApp): uri = url_concat(uri, {'token': self.one_time_token}) if browser: b = lambda : browser.open(url_path_join(self.connection_url, uri), - new=2) + new=self.webbrowser_open_new) threading.Thread(target=b).start() if self.token and self._token_generated: @@ -1409,7 +1483,7 @@ class NotebookApp(JupyterApp): try: self.io_loop.start() except KeyboardInterrupt: - info("Interrupted...") + info(_("Interrupted...")) finally: self.remove_server_info_file() self.cleanup_kernels() diff --git a/notebook/services/contents/filemanager.py b/notebook/services/contents/filemanager.py index d978d553f..0f7a0b246 100644 --- a/notebook/services/contents/filemanager.py +++ b/notebook/services/contents/filemanager.py @@ -3,12 +3,13 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. - +from datetime import datetime import errno import io import os import shutil import stat +import sys import warnings import mimetypes import nbformat @@ -18,6 +19,7 @@ from tornado import web from .filecheckpoints import FileCheckpoints from .fileio import FileManagerMixin from .manager import ContentsManager +from ...utils import exists from ipython_genutils.importstring import import_item from traitlets import Any, Unicode, Bool, TraitError, observe, default, validate @@ -28,6 +30,7 @@ from notebook.utils import ( is_hidden, is_file_hidden, to_api_path, ) +from notebook.base.handlers import AuthenticatedFileHandler try: from os.path import samefile @@ -142,9 +145,14 @@ class FileContentsManager(FileManagerMixin, ContentsManager): raise TraitError("%r is not a directory" % value) return value + @default('checkpoints_class') def _checkpoints_class_default(self): return FileCheckpoints + @default('files_handler_class') + def _files_handler_class_default(self): + return AuthenticatedFileHandler + def is_hidden(self, path): """Does the API style path correspond to a hidden directory or file? @@ -219,14 +227,25 @@ class FileContentsManager(FileManagerMixin, ContentsManager): """ path = path.strip('/') os_path = self._get_os_path(path=path) - return os.path.exists(os_path) + return exists(os_path) def _base_model(self, path): """Build the common base of a contents model""" os_path = self._get_os_path(path) - info = os.stat(os_path) - last_modified = tz.utcfromtimestamp(info.st_mtime) - created = tz.utcfromtimestamp(info.st_ctime) + info = os.lstat(os_path) + try: + last_modified = tz.utcfromtimestamp(info.st_mtime) + except ValueError: + # Files can rarely have an invalid timestamp + # https://github.com/jupyter/notebook/issues/2539 + # Use the Unix epoch as a fallback so we don't crash. + last_modified = datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC) + + try: + created = tz.utcfromtimestamp(info.st_ctime) + except ValueError: # See above + created = datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC) + # Create the base model. model = {} model['name'] = path.rsplit('/', 1)[-1] @@ -274,16 +293,18 @@ class FileContentsManager(FileManagerMixin, ContentsManager): continue try: - st = os.stat(os_path) + st = os.lstat(os_path) except OSError as e: # skip over broken symlinks in listing if e.errno == errno.ENOENT: self.log.warning("%s doesn't exist", os_path) else: - self.log.warning("Error stat-ing %s: %s", (os_path, e)) + self.log.warning("Error stat-ing %s: %s", os_path, e) continue - if not stat.S_ISREG(st.st_mode) and not stat.S_ISDIR(st.st_mode): + if (not stat.S_ISLNK(st.st_mode) + and not stat.S_ISREG(st.st_mode) + and not stat.S_ISDIR(st.st_mode)): self.log.debug("%s not a regular file", os_path) continue @@ -498,7 +519,7 @@ class FileContentsManager(FileManagerMixin, ContentsManager): raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e)) def info_string(self): - return "Serving notebooks from local directory: %s" % self.root_dir + return _("Serving notebooks from local directory: %s") % self.root_dir def get_kernel_path(self, path, model=None): """Return the initial API path of a kernel associated with a given notebook""" diff --git a/notebook/services/contents/handlers.py b/notebook/services/contents/handlers.py index d4e528abd..2b9cacd1a 100644 --- a/notebook/services/contents/handlers.py +++ b/notebook/services/contents/handlers.py @@ -18,17 +18,6 @@ from notebook.base.handlers import ( ) -def sort_key(model): - """key function for case-insensitive sort by name and type""" - iname = model['name'].lower() - type_key = { - 'directory' : '0', - 'notebook' : '1', - 'file' : '2', - }.get(model['type'], '9') - return u'%s%s' % (type_key, iname) - - def validate_model(model, expect_content): """ Validate a model returned by a ContentsManager method. @@ -123,10 +112,6 @@ class ContentsHandler(APIHandler): model = yield gen.maybe_future(self.contents_manager.get( path=path, type=type, format=format, content=content, )) - if model['type'] == 'directory' and content: - # group listing by type, then by name (case-insensitive) - # FIXME: sorting should be done in the frontends - model['content'].sort(key=sort_key) validate_model(model, expect_content=content) self._finish_model(model, location=False) @@ -176,7 +161,9 @@ class ContentsHandler(APIHandler): @gen.coroutine def _save(self, model, path): """Save an existing file.""" - self.log.info(u"Saving file at %s", path) + chunk = model.get("chunk", None) + if not chunk or chunk == -1: # Avoid tedious log information + self.log.info(u"Saving file at %s", path) model = yield gen.maybe_future(self.contents_manager.save(model, path)) validate_model(model, expect_content=False) self._finish_model(model) diff --git a/notebook/services/contents/largefilemanager.py b/notebook/services/contents/largefilemanager.py new file mode 100644 index 000000000..10808ba83 --- /dev/null +++ b/notebook/services/contents/largefilemanager.py @@ -0,0 +1,70 @@ +from notebook.services.contents.filemanager import FileContentsManager +from contextlib import contextmanager +from tornado import web +import nbformat +import base64 +import os, io + +class LargeFileManager(FileContentsManager): + """Handle large file upload.""" + + def save(self, model, path=''): + """Save the file model and return the model with no content.""" + chunk = model.get('chunk', None) + if chunk is not None: + path = path.strip('/') + + if 'type' not in model: + raise web.HTTPError(400, u'No file type provided') + if model['type'] != 'file': + raise web.HTTPError(400, u'File type "{}" is not supported for large file transfer'.format(model['type'])) + if 'content' not in model and model['type'] != 'directory': + raise web.HTTPError(400, u'No file content provided') + + os_path = self._get_os_path(path) + + try: + if chunk == 1: + self.log.debug("Saving %s", os_path) + self.run_pre_save_hook(model=model, path=path) + super(LargeFileManager, self)._save_file(os_path, model['content'], model.get('format')) + else: + self._save_large_file(os_path, model['content'], model.get('format')) + except web.HTTPError: + raise + except Exception as e: + self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True) + raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e)) + + model = self.get(path, content=False) + + # Last chunk + if chunk == -1: + self.run_post_save_hook(model=model, os_path=os_path) + return model + else: + return super(LargeFileManager, self).save(model, path) + + def _save_large_file(self, os_path, content, format): + """Save content of a generic file.""" + if format not in {'text', 'base64'}: + raise web.HTTPError( + 400, + "Must specify format of file contents as 'text' or 'base64'", + ) + try: + if format == 'text': + bcontent = content.encode('utf8') + else: + b64_bytes = content.encode('ascii') + bcontent = base64.b64decode(b64_bytes) + except Exception as e: + raise web.HTTPError( + 400, u'Encoding error saving %s: %s' % (os_path, e) + ) + + with self.perm_to_403(os_path): + if os.path.islink(os_path): + os_path = os.path.join(os.path.dirname(os_path), os.readlink(os_path)) + with io.open(os_path, 'ab') as f: + f.write(bcontent) diff --git a/notebook/services/contents/manager.py b/notebook/services/contents/manager.py index 8918b474a..09a00e911 100644 --- a/notebook/services/contents/manager.py +++ b/notebook/services/contents/manager.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. from fnmatch import fnmatch +import gettext import itertools import json import os @@ -28,6 +29,7 @@ from traitlets import ( default, ) from ipython_genutils.py3compat import string_types +from notebook.base.handlers import IPythonHandler copy_pat = re.compile(r'\-Copy\d*\.') @@ -50,6 +52,8 @@ class ContentsManager(LoggingConfigurable): indicating the root path. """ + + root_dir = Unicode('/', config=True) notary = Instance(sign.NotebookNotary) def _notary_default(self): @@ -62,7 +66,7 @@ class ContentsManager(LoggingConfigurable): Glob patterns to hide in file and directory listings. """) - untitled_notebook = Unicode("Untitled", config=True, + untitled_notebook = Unicode(_("Untitled"), config=True, help="The base name used when creating untitled notebooks." ) @@ -127,6 +131,8 @@ class ContentsManager(LoggingConfigurable): log=self.log, ) + files_handler_class = Type(IPythonHandler, allow_none=True, config=True) + # ContentsManager API part 1: methods that must be # implemented in subclasses. diff --git a/notebook/services/contents/tests/test_contents_api.py b/notebook/services/contents/tests/test_contents_api.py index 439dffdfa..990b6ca51 100644 --- a/notebook/services/contents/tests/test_contents_api.py +++ b/notebook/services/contents/tests/test_contents_api.py @@ -249,8 +249,8 @@ class APITest(NotebookTestBase): self.assertEqual(nbnames, expected) nbs = notebooks_only(self.api.list('ordering').json()) - nbnames = [n['name'] for n in nbs] - expected = ['A.ipynb', 'b.ipynb', 'C.ipynb'] + nbnames = {n['name'] for n in nbs} + expected = {'A.ipynb', 'b.ipynb', 'C.ipynb'} self.assertEqual(nbnames, expected) def test_list_dirs(self): diff --git a/notebook/services/contents/tests/test_largefilemanager.py b/notebook/services/contents/tests/test_largefilemanager.py new file mode 100644 index 000000000..13d294b9b --- /dev/null +++ b/notebook/services/contents/tests/test_largefilemanager.py @@ -0,0 +1,113 @@ +from unittest import TestCase +from ipython_genutils.tempdir import TemporaryDirectory +from ..largefilemanager import LargeFileManager +import os +from tornado import web + + +def _make_dir(contents_manager, api_path): + """ + Make a directory. + """ + os_path = contents_manager._get_os_path(api_path) + try: + os.makedirs(os_path) + except OSError: + print("Directory already exists: %r" % os_path) + + +class TestLargeFileManager(TestCase): + + def setUp(self): + self._temp_dir = TemporaryDirectory() + self.td = self._temp_dir.name + self.contents_manager = LargeFileManager(root_dir=self.td) + + def make_dir(self, api_path): + """make a subdirectory at api_path + + override in subclasses if contents are not on the filesystem. + """ + _make_dir(self.contents_manager, api_path) + + def test_save(self): + + cm = self.contents_manager + # Create a notebook + model = cm.new_untitled(type='notebook') + name = model['name'] + path = model['path'] + + # Get the model with 'content' + full_model = cm.get(path) + # Save the notebook + model = cm.save(full_model, path) + assert isinstance(model, dict) + self.assertIn('name', model) + self.assertIn('path', model) + self.assertEqual(model['name'], name) + self.assertEqual(model['path'], path) + + try: + model = {'name': 'test', 'path': 'test', 'chunk': 1} + cm.save(model, model['path']) + except web.HTTPError as e: + self.assertEqual('HTTP 400: Bad Request (No file type provided)', str(e)) + + try: + model = {'name': 'test', 'path': 'test', 'chunk': 1, 'type': 'notebook'} + cm.save(model, model['path']) + except web.HTTPError as e: + self.assertEqual('HTTP 400: Bad Request (File type "notebook" is not supported for large file transfer)', str(e)) + + try: + model = {'name': 'test', 'path': 'test', 'chunk': 1, 'type': 'file'} + cm.save(model, model['path']) + except web.HTTPError as e: + self.assertEqual('HTTP 400: Bad Request (No file content provided)', str(e)) + + try: + model = {'name': 'test', 'path': 'test', 'chunk': 2, 'type': 'file', + 'content': u'test', 'format': 'json'} + cm.save(model, model['path']) + except web.HTTPError as e: + self.assertEqual("HTTP 400: Bad Request (Must specify format of file contents as 'text' or 'base64')", + str(e)) + + # Save model for different chunks + model = {'name': 'test', 'path': 'test', 'type': 'file', + 'content': u'test==', 'format': 'text'} + name = model['name'] + path = model['path'] + cm.save(model, path) + + for chunk in (1, 2, -1): + for fm in ('text', 'base64'): + full_model = cm.get(path) + full_model['chunk'] = chunk + full_model['format'] = fm + model_res = cm.save(full_model, path) + assert isinstance(model_res, dict) + + self.assertIn('name', model_res) + self.assertIn('path', model_res) + self.assertNotIn('chunk', model_res) + self.assertEqual(model_res['name'], name) + self.assertEqual(model_res['path'], path) + + # Test in sub-directory + # Create a directory and notebook in that directory + sub_dir = '/foo/' + self.make_dir('foo') + model = cm.new_untitled(path=sub_dir, type='notebook') + name = model['name'] + path = model['path'] + model = cm.get(path) + + # Change the name in the model for rename + model = cm.save(model, path) + assert isinstance(model, dict) + self.assertIn('name', model) + self.assertIn('path', model) + self.assertEqual(model['name'], 'Untitled.ipynb') + self.assertEqual(model['path'], 'foo/Untitled.ipynb') diff --git a/notebook/services/contents/tests/test_manager.py b/notebook/services/contents/tests/test_manager.py index c13e60fa7..0b4e5a4ea 100644 --- a/notebook/services/contents/tests/test_manager.py +++ b/notebook/services/contents/tests/test_manager.py @@ -108,7 +108,7 @@ class TestFileContentsManager(TestCase): self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name)) self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name)) - @dec.skip_win32 + @dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3) def test_bad_symlink(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -120,9 +120,16 @@ class TestFileContentsManager(TestCase): # create a broken symlink self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink')) model = cm.get(path) - self.assertEqual(model['content'], [file_model]) - @dec.skip_win32 + contents = { + content['name']: content for content in model['content'] + } + self.assertTrue('untitled.txt' in contents) + self.assertEqual(contents['untitled.txt'], file_model) + # broken symlinks should still be shown in the contents manager + self.assertTrue('bad symlink' in contents) + + @dec.skipif(sys.platform == 'win32' and sys.version_info[0] < 3) def test_good_symlink(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -224,7 +231,7 @@ class TestContentsManager(TestCase): self.assertEqual(entry['name'], "nb.ipynb") complete_path = "/".join([api_path, "nb.ipynb"]) self.assertEqual(entry["path"], complete_path) - + def setUp(self): self._temp_dir = TemporaryDirectory() self.td = self._temp_dir.name diff --git a/notebook/services/kernels/handlers.py b/notebook/services/kernels/handlers.py index 4b35f5a14..8ba8cd038 100644 --- a/notebook/services/kernels/handlers.py +++ b/notebook/services/kernels/handlers.py @@ -336,7 +336,10 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler): # Increment the bytes and message count self._iopub_window_msg_count += 1 - byte_count = sum([len(x) for x in msg_list]) + if msg_type == 'stream': + byte_count = sum([len(x) for x in msg_list]) + else: + byte_count = 0 self._iopub_window_byte_count += byte_count # Queue a removal of the byte and message count for a time in the @@ -357,7 +360,12 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler): The notebook server will temporarily stop sending output to the client in order to avoid crashing it. To change this limit, set the config variable - `--NotebookApp.iopub_msg_rate_limit`.""")) + `--NotebookApp.iopub_msg_rate_limit`. + + Current values: + NotebookApp.iopub_msg_rate_limit={} (msgs/sec) + NotebookApp.rate_limit_window={} (secs) + """.format(self.iopub_msg_rate_limit, self.rate_limit_window))) else: # resume once we've got some headroom below the limit if self._iopub_msgs_exceeded and msg_rate < (0.8 * self.iopub_msg_rate_limit): @@ -374,7 +382,12 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler): The notebook server will temporarily stop sending output to the client in order to avoid crashing it. To change this limit, set the config variable - `--NotebookApp.iopub_data_rate_limit`.""")) + `--NotebookApp.iopub_data_rate_limit`. + + Current values: + NotebookApp.iopub_data_rate_limit={} (bytes/sec) + NotebookApp.rate_limit_window={} (secs) + """.format(self.iopub_data_rate_limit, self.rate_limit_window))) else: # resume once we've got some headroom below the limit if self._iopub_data_exceeded and data_rate < (0.8 * self.iopub_data_rate_limit): diff --git a/notebook/services/kernels/kernelmanager.py b/notebook/services/kernels/kernelmanager.py index b5c4c9f5d..2453d6f05 100644 --- a/notebook/services/kernels/kernelmanager.py +++ b/notebook/services/kernels/kernelmanager.py @@ -11,15 +11,18 @@ import os from tornado import gen, web from tornado.concurrent import Future -from tornado.ioloop import IOLoop +from tornado.ioloop import IOLoop, PeriodicCallback +from jupyter_client.session import Session from jupyter_client.multikernelmanager import MultiKernelManager -from traitlets import Dict, List, Unicode, TraitError, default, validate +from traitlets import Bool, Dict, List, Unicode, TraitError, Integer, default, validate -from notebook.utils import to_os_path +from notebook.utils import to_os_path, exists from notebook._tz import utcnow, isoformat from ipython_genutils.py3compat import getcwd +from datetime import datetime, timedelta + class MappingKernelManager(MultiKernelManager): """A KernelManager that handles notebook mapping and HTTP error handling""" @@ -34,6 +37,10 @@ class MappingKernelManager(MultiKernelManager): _kernel_connections = Dict() + _culler_callback = None + + _initialized_culler = False + @default('root_dir') def _default_root_dir(self): try: @@ -48,10 +55,32 @@ class MappingKernelManager(MultiKernelManager): if not os.path.isabs(value): # If we receive a non-absolute path, make it absolute. value = os.path.abspath(value) - if not os.path.exists(value) or not os.path.isdir(value): + if not exists(value) or not os.path.isdir(value): raise TraitError("kernel root dir %r is not a directory" % value) return value + cull_idle_timeout_minimum = 300 # 5 minutes + cull_idle_timeout = Integer(0, config=True, + help="""Timeout (in seconds) after which a kernel is considered idle and ready to be culled. Values of 0 or + lower disable culling. The minimum timeout is 300 seconds (5 minutes). Positive values less than the minimum value + will be set to the minimum.""" + ) + + cull_interval_default = 300 # 5 minutes + cull_interval = Integer(cull_interval_default, config=True, + help="""The interval (in seconds) on which to check for idle kernels exceeding the cull timeout value.""" + ) + + cull_connected = Bool(False, config=True, + help="""Whether to consider culling kernels which have one or more connections. + Only effective if cull_idle_timeout is not 0.""" + ) + + cull_busy = Bool(False, config=True, + help="""Whether to consider culling kernels which are busy. + Only effective if cull_idle_timeout is not 0.""" + ) + #------------------------------------------------------------------------- # Methods for managing kernels and sessions #------------------------------------------------------------------------- @@ -105,6 +134,11 @@ class MappingKernelManager(MultiKernelManager): else: self._check_kernel_id(kernel_id) self.log.info("Using existing kernel: %s" % kernel_id) + + # Initialize culling if not already + if not self._initialized_culler: + self.initialize_culler() + # py2-compat raise gen.Return(kernel_id) @@ -211,13 +245,17 @@ class MappingKernelManager(MultiKernelManager): kernel.execution_state = 'starting' kernel.last_activity = utcnow() kernel._activity_stream = kernel.connect_iopub() + session = Session( + config=kernel.session.config, + key=kernel.session.key, + ) def record_activity(msg_list): """Record an IOPub message arriving from a kernel""" kernel.last_activity = utcnow() - idents, fed_msg_list = kernel.session.feed_identities(msg_list) - msg = kernel.session.deserialize(fed_msg_list) + idents, fed_msg_list = session.feed_identities(msg_list) + msg = session.deserialize(fed_msg_list) msg_type = msg['header']['msg_type'] self.log.debug("activity on %s: %s", kernel_id, msg_type) if msg_type == 'status': @@ -225,3 +263,60 @@ class MappingKernelManager(MultiKernelManager): kernel._activity_stream.on_recv(record_activity) + def initialize_culler(self): + """Start idle culler if 'cull_idle_timeout' is greater than zero. + + Regardless of that value, set flag that we've been here. + """ + if not self._initialized_culler and self.cull_idle_timeout > 0: + if self._culler_callback is None: + if self.cull_idle_timeout < self.cull_idle_timeout_minimum: + self.log.warning("'cull_idle_timeout' (%s) is less than the minimum value (%s) and has been set to the minimum.", + self.cull_idle_timeout, self.cull_idle_timeout_minimum) + self.cull_idle_timeout = self.cull_idle_timeout_minimum + loop = IOLoop.current() + if self.cull_interval <= 0: #handle case where user set invalid value + self.log.warning("Invalid value for 'cull_interval' detected (%s) - using default value (%s).", + self.cull_interval, self.cull_interval_default) + self.cull_interval = self.cull_interval_default + self._culler_callback = PeriodicCallback( + self.cull_kernels, 1000*self.cull_interval, loop) + self.log.info("Culling kernels with idle durations > %s seconds at %s second intervals ...", + self.cull_idle_timeout, self.cull_interval) + if self.cull_busy: + self.log.info("Culling kernels even if busy") + if self.cull_connected: + self.log.info("Culling kernels even with connected clients") + self._culler_callback.start() + + self._initialized_culler = True + + def cull_kernels(self): + self.log.debug("Polling every %s seconds for kernels idle > %s seconds...", + self.cull_interval, self.cull_idle_timeout) + """Create a separate list of kernels to avoid conflicting updates while iterating""" + for kernel_id in list(self._kernels): + try: + self.cull_kernel_if_idle(kernel_id) + except Exception as e: + self.log.exception("The following exception was encountered while checking the idle duration of kernel %s: %s", + kernel_id, e) + + def cull_kernel_if_idle(self, kernel_id): + kernel = self._kernels[kernel_id] + self.log.debug("kernel_id=%s, kernel_name=%s, last_activity=%s", kernel_id, kernel.kernel_name, kernel.last_activity) + if kernel.last_activity is not None: + dt_now = utcnow() + dt_idle = dt_now - kernel.last_activity + # Compute idle properties + is_idle_time = dt_idle > timedelta(seconds=self.cull_idle_timeout) + is_idle_execute = self.cull_busy or (kernel.execution_state != 'busy') + connections = self._kernel_connections.get(kernel_id, 0) + is_idle_connected = self.cull_connected or not connections + # Cull the kernel if all three criteria are met + if (is_idle_time and is_idle_execute and is_idle_connected): + idle_duration = int(dt_idle.total_seconds()) + self.log.warning("Culling '%s' kernel '%s' (%s) with %d connections due to %s seconds of inactivity.", + kernel.execution_state, kernel.kernel_name, kernel_id, connections, idle_duration) + self.shutdown_kernel(kernel_id) + diff --git a/notebook/services/security/handlers.py b/notebook/services/security/handlers.py index f20e18058..d8f38feff 100644 --- a/notebook/services/security/handlers.py +++ b/notebook/services/security/handlers.py @@ -13,10 +13,14 @@ class CSPReportHandler(APIHandler): _track_activity = False - def skip_origin_check(self): + def skip_check_origin(self): """Don't check origin when reporting origin-check violations!""" return True + def check_xsrf_cookie(self): + # don't check XSRF for CSP reports + return + @json_errors @web.authenticated def post(self): diff --git a/notebook/services/shutdown.py b/notebook/services/shutdown.py new file mode 100644 index 000000000..78d1f2ad6 --- /dev/null +++ b/notebook/services/shutdown.py @@ -0,0 +1,15 @@ +"""HTTP handler to shut down the notebook server. +""" +from tornado import web, ioloop +from notebook.base.handlers import IPythonHandler + +class ShutdownHandler(IPythonHandler): + @web.authenticated + def post(self): + self.log.info("Shutting down on /api/shutdown request.") + ioloop.IOLoop.current().stop() + + +default_handlers = [ + (r"/api/shutdown", ShutdownHandler), +] diff --git a/notebook/static/auth/js/loginmain.js b/notebook/static/auth/js/loginmain.js index 1a158483c..ab9e51e4f 100644 --- a/notebook/static/auth/js/loginmain.js +++ b/notebook/static/auth/js/loginmain.js @@ -3,7 +3,7 @@ define(['jquery', 'base/js/namespace', 'base/js/page'], function($, IPython, page) { function login_main() { - var page_instance = new page.Page(); + var page_instance = new page.Page('div#header', 'div#site'); $('button#login_submit').addClass("btn btn-default"); page_instance.show(); $('input#password_input').focus(); diff --git a/notebook/static/auth/js/logoutmain.js b/notebook/static/auth/js/logoutmain.js index 7b3f6b4da..41e775053 100644 --- a/notebook/static/auth/js/logoutmain.js +++ b/notebook/static/auth/js/logoutmain.js @@ -3,7 +3,7 @@ define(['base/js/namespace', 'base/js/page'], function(IPython, page) { function logout_main() { - var page_instance = new page.Page(); + var page_instance = new page.Page('div#header', 'div#site'); page_instance.show(); IPython.page = page_instance; diff --git a/notebook/static/base/images/favicon-busy-1.ico b/notebook/static/base/images/favicon-busy-1.ico new file mode 100644 index 000000000..5b46a8226 Binary files /dev/null and b/notebook/static/base/images/favicon-busy-1.ico differ diff --git a/notebook/static/base/images/favicon-busy-2.ico b/notebook/static/base/images/favicon-busy-2.ico new file mode 100644 index 000000000..4a8b841c2 Binary files /dev/null and b/notebook/static/base/images/favicon-busy-2.ico differ diff --git a/notebook/static/base/images/favicon-busy-3.ico b/notebook/static/base/images/favicon-busy-3.ico new file mode 100644 index 000000000..b5edce573 Binary files /dev/null and b/notebook/static/base/images/favicon-busy-3.ico differ diff --git a/notebook/static/base/images/favicon-busy.ico b/notebook/static/base/images/favicon-busy.ico deleted file mode 100644 index 85f9995a4..000000000 Binary files a/notebook/static/base/images/favicon-busy.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-file.ico b/notebook/static/base/images/favicon-file.ico new file mode 100644 index 000000000..8167018cd Binary files /dev/null and b/notebook/static/base/images/favicon-file.ico differ diff --git a/notebook/static/base/images/favicon-notebook.ico b/notebook/static/base/images/favicon-notebook.ico new file mode 100644 index 000000000..4537e2d98 Binary files /dev/null and b/notebook/static/base/images/favicon-notebook.ico differ diff --git a/notebook/static/base/images/favicon-terminal.ico b/notebook/static/base/images/favicon-terminal.ico new file mode 100644 index 000000000..ace499a33 Binary files /dev/null and b/notebook/static/base/images/favicon-terminal.ico differ diff --git a/notebook/static/base/js/dialog.js b/notebook/static/base/js/dialog.js index 96190548c..b59d9908b 100644 --- a/notebook/static/base/js/dialog.js +++ b/notebook/static/base/js/dialog.js @@ -1,13 +1,13 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -define(function(require) { +define(['jquery', + 'codemirror/lib/codemirror', + 'bootstrap', + 'base/js/i18n'], + function($, CodeMirror, bs, i18n) { "use strict"; - var CodeMirror = require('codemirror/lib/codemirror'); - var bs = require('bootstrap'); - var $ = require('jquery'); - /** * A wrapper around bootstrap modal for easier use * Pass it an option dictionary with the following properties: @@ -86,7 +86,7 @@ define(function(require) { var button = $("