Merge pull request #63 from cdubz/i18n

Make Baby Buddy translatable
This commit is contained in:
Christopher Charbonneau Wells 2019-04-18 20:36:37 -07:00 committed by GitHub
commit 22536f88cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 2610 additions and 594 deletions

View File

@ -7,6 +7,7 @@
- [Contributions](#contributions)
- [Pull request process](#pull-request-process)
- [Translation](#translation)
- [Development](#development)
- [Installation](#installation)
- [Gulp Commands](#gulp-commands)
@ -34,6 +35,33 @@ Gitter.
New pull requests will be reviewed by project maintainers as soon as possible
and we will do our best to provide feedback and support potential contributors.
### Translation
Baby Buddy has support for translation/localization to any language. A general
translation process will look something like this:
1. Set up a development environment (see [Development](#development) below).
1. Run `gulp makemessages -l xx` where `xx` is a specific locale code (e.g. "fr"
for French or "es" for Spanish). This create a new translation file at
`locale/xx/LC_MESSAGES/django.po`, or update one if it exits.
1. Open the created/updated `django.po` file and update the header template with
license and contact info.
1. Start translating! Each translatable string will have a `msgid` value with
the string in English and a corresponding (empty) `msgstr` value where a
translated string can be filled in.
1. Once all strings have been translated, run `gulp compilemessages -l xx` to
compile an optimized translation file (`locale/xx/LC_MESSAGES/django.mo`).
1. To expose the new translation as a user setting, add the locale code to the
`LANGUAGES` array in the base settings file (`babybuddy/settings/base.py`).
1. Run the development server, log in, and update the user language to test the
newly translated strings.
Once the translation is complete, commit the new files and changes to a fork and
[create a pull request](#pull-request-process) for review.
For more information on the Django translation process, see Django's
documentation section: [Translation](https://docs.djangoproject.com/en/dev/topics/i18n/translation/).
## Development
### Requirements
@ -92,10 +120,12 @@ in the [`babybuddy/management/commands`](babybuddy/management/commands) folder.
- [`gulp build`](#build)
- [`gulp clean`](#clean)
- [`gulp collectstatic`](#collectstatic)
- [`gulp compilemessages`](#compilemessages)
- [`gulp coverage`](#coverage)
- [`gulp extras`](#extras)
- [`gulp fake`](#fake)
- [`gulp lint`](#lint)
- [`gulp makemessages`](#makemessages)
- [`gulp makemigrations`](#makemigrations)
- [`gulp migrate`](#migrate)
- [`gulp reset`](#reset)
@ -126,6 +156,11 @@ the `babybuddy/static` folder, so generally `gulp build` should be run before
this command for production deployments. Gulp also passes along
non-overlapping arguments for this command, e.g. `--no-input`.
#### `compilemessages`
Executes Django's `compilemessages` management task. See [django-admin compilemessages](https://docs.djangoproject.com/en/dev/ref/django-admin/#compilemessages)
for more details about other options and functionality of this command.
#### `coverage`
Create a test coverage report. See [`.coveragerc`](.coveragerc) for default
@ -147,6 +182,14 @@ children and seven days of data for each.
Executes Python and SASS linting for all relevant source files.
#### `makemessages`
Executes Django's `makemessages` management task. See [django-admin makemessages](https://docs.djangoproject.com/en/dev/ref/django-admin/#makemessages)
for more details about other options and functionality of this command. When
working on a single translation, the `-l` flag is useful to make message for
only that language, e.g. `gulp makemessages -l fr` to make only a French
language translation file.
#### `makemigrations`
Executes Django's `makemigrations` management task. Gulp also passes along

View File

@ -2,16 +2,18 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
from babybuddy import models
class SettingsInline(admin.StackedInline):
model = models.Settings
verbose_name_plural = 'Settings'
verbose_name = _('Settings')
verbose_name_plural = _('Settings')
can_delete = False
fieldsets = (
('Dashboard', {
(_('Dashboard'), {
'fields': ('dashboard_refresh_rate',)
}),
)

View File

@ -41,4 +41,4 @@ class UserPasswordForm(PasswordChangeForm):
class UserSettingsForm(forms.ModelForm):
class Meta:
model = Settings
fields = ['dashboard_refresh_rate']
fields = ['dashboard_refresh_rate', 'language']

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2 on 2019-04-17 04:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('babybuddy', '0003_add_refresh_help_text'),
]
operations = [
migrations.AddField(
model_name='settings',
name='language',
field=models.CharField(choices=[], default='en', max_length=255, verbose_name='Language'),
),
]

View File

@ -1,9 +1,14 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.timezone import timedelta
from django.utils.text import format_lazy
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import Token
@ -11,26 +16,32 @@ from rest_framework.authtoken.models import Token
class Settings(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dashboard_refresh_rate = models.DurationField(
verbose_name='Refresh rate',
help_text='This setting will only be used when a browser does not '
'support refresh on focus.',
verbose_name=_('Refresh rate'),
help_text=_('This setting will only be used when a browser does not '
'support refresh on focus.'),
blank=True,
null=True,
default=timedelta(minutes=1),
choices=[
(None, 'disabled'),
(timedelta(minutes=1), '1 min.'),
(timedelta(minutes=2), '2 min.'),
(timedelta(minutes=3), '3 min.'),
(timedelta(minutes=4), '4 min.'),
(timedelta(minutes=5), '5 min.'),
(timedelta(minutes=10), '10 min.'),
(timedelta(minutes=15), '15 min.'),
(timedelta(minutes=30), '30 min.'),
(None, _('disabled')),
(timedelta(minutes=1), _('1 min.')),
(timedelta(minutes=2), _('2 min.')),
(timedelta(minutes=3), _('3 min.')),
(timedelta(minutes=4), _('4 min.')),
(timedelta(minutes=5), _('5 min.')),
(timedelta(minutes=10), _('10 min.')),
(timedelta(minutes=15), _('15 min.')),
(timedelta(minutes=30), _('30 min.')),
])
language = models.CharField(
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE,
max_length=255,
verbose_name=_('Language')
)
def __str__(self):
return '{}\'s Settings'.format(self.user)
return str(format_lazy(_('{user}\'s Settings'), user=self.user))
def api_key(self, reset=False):
"""
@ -63,3 +74,9 @@ def create_user_settings(sender, instance, created, **kwargs):
@receiver(post_save, sender=User)
def save_user_settings(sender, instance, **kwargs):
instance.settings.save()
@receiver(user_logged_in)
def user_logged_in_callback(sender, request, user, **kwargs):
translation.activate(user.settings.language)
request.session[translation.LANGUAGE_SESSION_KEY] = user.settings.language

View File

@ -1,5 +1,6 @@
import os
from django.utils.translation import gettext_lazy as _
from dotenv import load_dotenv, find_dotenv
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@ -53,10 +54,9 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -110,7 +110,7 @@ LOGOUT_REDIRECT_URL = '/login/'
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en'
TIME_ZONE = os.environ.get('TIME_ZONE', 'Etc/UTC')
@ -120,6 +120,15 @@ USE_L10N = True
USE_TZ = True
LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"),
]
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

View File

@ -1,15 +1,15 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}403 Permission Denied{% endblock %}
{% block title %}403 {% trans "Permission Denied" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Permission Denied</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Permission Denied" %}</li>
{% endblock %}
{% block content %}
<div class="alert alert-danger" role="alert">
You do not have permission to access this resource. Contact a site
administrator for assistance.
{% blocktrans %}You do not have permission to access this resource.
Contact a site administrator for assistance.{% endblocktrans %}
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% load static %}
{% load i18n static %}
<!DOCTYPE html>
<html lang="en">
@ -31,7 +31,7 @@
{% block breadcrumb_nav %}
<nav aria-label="breadcrumb" role="navigation">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/">{% trans "Home" %}</a></li>
{% block breadcrumbs %}{% endblock %}
</ol>
</nav>

View File

@ -1,4 +1,4 @@
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
<form id="filter_form" role="form" action="" method="get" class="collapse{% if request.GET.filtered %} show{% endif %}">
<div class="form-group form-row">
@ -15,8 +15,8 @@
</div>
{% endfor %}
<div class="col-xs-12 col-sm-auto mt-3 mt-sm-0">
<button type="submit" class="btn btn-sm btn-primary mr-2">Filter</button>
<a href="{{ request.path }}" class="btn btn-sm btn-error">Reset</a>
<button type="submit" class="btn btn-sm btn-primary mr-2">{% trans "Filter" %}</button>
<a href="{{ request.path }}" class="btn btn-sm btn-error">{% trans "Reset" %}</a>
</div>
</div>
<input type="hidden" name="filtered" value="1"/>
@ -29,6 +29,6 @@
role="button"
aria-expanded="false"
aria-controls="filter_form">
Filters
{% trans "Filters" %}
</a>
</p>

View File

@ -1,4 +1,4 @@
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
<div class="container-fluid">
<form role="form" method="post" enctype="multipart/form-data">
@ -8,6 +8,6 @@
{% include 'babybuddy/form_field.html' %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Submit</button>
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
</form>
</div>

View File

@ -1,3 +1,5 @@
{% load i18n %}
{% block messages %}
{% if messages %}
{% for message in messages %}
@ -13,12 +15,12 @@
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> {{ error }}
{% blocktrans %}<strong>Error:</strong> {{ error }}{% endblocktrans %}
</div>
{% endfor %}
{% elif form.errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> Some fields have errors. See below for details.
{% blocktrans %}<strong>Error:</strong> Some fields have errors. See below for details. {% endblocktrans %}
</div>
{% endif %}
{% endif %}

View File

@ -1,5 +1,5 @@
{% extends 'babybuddy/base.html' %}
{% load babybuddy_tags static timers %}
{% load babybuddy_tags i18n static timers %}
{% block nav %}
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
@ -23,32 +23,38 @@
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-quick-add-link">
{% if perms.core.add_diaperchange %}
<a class="dropdown-item p-2" href="{% url 'core:diaperchange-add' %}">
<i class="icon icon-diaperchange" aria-hidden="true"></i> Diaper Change
<i class="icon icon-diaperchange" aria-hidden="true"></i>
{% trans "Diaper Change" %}
</a>
{% endif %}
{% if perms.core.add_feeding %}
<a class="dropdown-item p-2" href="{% url 'core:feeding-add' %}">
<i class="icon icon-feeding" aria-hidden="true"></i> Feeding
<i class="icon icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.add_note %}
<a class="dropdown-item p-2" href="{% url 'core:note-add' %}">
<i class="icon icon-note" aria-hidden="true"></i> Note
<i class="icon icon-note" aria-hidden="true"></i>
{% trans "Note" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="dropdown-item p-2" href="{% url 'core:sleep-add' %}">
<i class="icon icon-sleep" aria-hidden="true"></i> Sleep
<i class="icon icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="dropdown-item p-2" href="{% url 'core:tummytime-add' %}">
<i class="icon icon-tummytime" aria-hidden="true"></i> Tummy Time
<i class="icon icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
{% if perms.core.add_weight %}
<a class="dropdown-item p-2" href="{% url 'core:weight-add' %}">
<i class="icon icon-weight" aria-hidden="true"></i> Weight
<i class="icon icon-weight" aria-hidden="true"></i>
{% trans "Weight" %}
</a>
{% endif %}
</div>
@ -66,7 +72,8 @@
<ul class="navbar-nav mr-auto">
<li class="nav-item{% if request.path == '/' %} active{% endif %}">
<a class="nav-link" href="{% url 'dashboard:dashboard' %}">
<i class="icon icon-dashboard" aria-hidden="true"></i> Dashboard
<i class="icon icon-dashboard" aria-hidden="true"></i>
{% trans "Dashboard" %}
</a>
</li>
@ -76,40 +83,51 @@
href="#"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon icon-child" aria-hidden="true"></i> Children</a>
aria-expanded="false"><i class="icon icon-child" aria-hidden="true"></i>
{% trans "Children" %}
</a>
<div class="dropdown-menu" aria-labelledby="nav-children-menu-link">
{% if perms.core.view_child %}
<a class="dropdown-item{% if request.path == '/children/' %} active{% endif %}"
href="{% url 'core:child-list' %}">
<i class="icon icon-child" aria-hidden="true"></i> Children
<i class="icon icon-child" aria-hidden="true"></i>
{% trans "Children" %}
</a>
{% endif %}
{% if perms.core.add_child %}
<a class="dropdown-item pl-5{% if request.path == '/children/add/' %} active{% endif %}"
href="{% url 'core:child-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Child</a>
href="{% url 'core:child-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Child" %}
</a>
{% endif %}
{% if perms.core.view_note %}
<a class="dropdown-item{% if request.path == '/notes/' %} active{% endif %}"
href="{% url 'core:note-list' %}">
<i class="icon icon-note" aria-hidden="true"></i> Notes
<i class="icon icon-note" aria-hidden="true"></i>
{% trans "Notes" %}
</a>
{% endif %}
{% if perms.core.add_note %}
<a class="dropdown-item pl-5{% if request.path == '/notes/add/' %} active{% endif %}"
href="{% url 'core:note-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Note</a>
href="{% url 'core:note-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Note" %}
</a>
{% endif %}
{% if perms.core.view_weight %}
<a class="dropdown-item{% if request.path == '/weight/' %} active{% endif %}"
href="{% url 'core:weight-list' %}">
<i class="icon icon-weight" aria-hidden="true"></i> Weight
<i class="icon icon-weight" aria-hidden="true"></i>
{% trans "Weight" %}
</a>
{% endif %}
{% if perms.core.add_weight %}
<a class="dropdown-item pl-5{% if request.path == '/weight/add/' %} active{% endif %}"
href="{% url 'core:weight-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Weight entry</a>
href="{% url 'core:weight-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Weight entry" %}
</a>
{% endif %}
</div>
@ -121,43 +139,61 @@
href="#"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon icon-activities" aria-hidden="true"></i> Activities</a>
aria-expanded="false"><i class="icon icon-activities" aria-hidden="true"></i>
{% trans "Activities" %}
</a>
<div class="dropdown-menu" aria-labelledby="nav-activity-menu-link">
{% if perms.core.view_diaperchange %}
<a class="dropdown-item{% if request.path == '/changes/' %} active{% endif %}"
href="{% url 'core:diaperchange-list' %}"><i class="icon icon-diaperchange" aria-hidden="true"></i> Changes</a>
href="{% url 'core:diaperchange-list' %}"><i class="icon icon-diaperchange" aria-hidden="true"></i>
{% trans "Changes" %}
</a>
{% endif %}
{% if perms.core.add_diaperchange %}
<a class="dropdown-item pl-5{% if request.path == '/changes/add/' %} active{% endif %}"
href="{% url 'core:diaperchange-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Change</a>
href="{% url 'core:diaperchange-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Change" %}
</a>
{% endif %}
{% if perms.core.view_feeding %}
<a class="dropdown-item{% if request.path == '/feedings/' %} active{% endif %}"
href="{% url 'core:feeding-list' %}"><i class="icon icon-feeding" aria-hidden="true"></i> Feedings</a>
href="{% url 'core:feeding-list' %}"><i class="icon icon-feeding" aria-hidden="true"></i>
{% trans "Feedings" %}
</a>
{% endif %}
{% if perms.core.add_diaperchange %}
<a class="dropdown-item pl-5{% if request.path == '/feedings/add/' %} active{% endif %}"
href="{% url 'core:feeding-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Feeding</a>
href="{% url 'core:feeding-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.view_sleep %}
<a class="dropdown-item{% if request.path == '/sleep/' %} active{% endif %}"
href="{% url 'core:sleep-list' %}"><i class="icon icon-sleep" aria-hidden="true"></i> Sleep</a>
href="{% url 'core:sleep-list' %}"><i class="icon icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="dropdown-item pl-5{% if request.path == '/sleep/add/' %} active{% endif %}"
href="{% url 'core:sleep-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Sleep entry</a>
href="{% url 'core:sleep-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Sleep entry" %}
</a>
{% endif %}
{% if perms.core.view_tummytime %}
<a class="dropdown-item{% if request.path == '/tummy-time/' %} active{% endif %}"
href="{% url 'core:tummytime-list' %}"><i class="icon icon-tummytime" aria-hidden="true"></i> Tummy Time</a>
href="{% url 'core:tummytime-list' %}"><i class="icon icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="dropdown-item pl-5{% if request.path == '/tummy-time/add/' %} active{% endif %}"
href="{% url 'core:tummytime-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Tummy Time entry</a>
href="{% url 'core:tummytime-add' %}"><i class="icon icon-add" aria-hidden="true"></i>
{% trans "Tummy Time entry" %}
</a>
{% endif %}
</div>
@ -179,29 +215,29 @@
aria-expanded="false"><i class="icon icon-user" aria-hidden="true"></i> {{ request.user }}
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-user-menu-link">
<h6 class="dropdown-header">User</h6>
<a href="{% url 'babybuddy:user-settings' %}" class="dropdown-item">Settings</a>
<a href="{% url 'babybuddy:user-password' %}" class="dropdown-item">Password</a>
<a href="{% url 'babybuddy:logout' %}" class="dropdown-item">Logout</a>
<h6 class="dropdown-header">Site</h6>
<h6 class="dropdown-header">{% trans "User" %}</h6>
<a href="{% url 'babybuddy:user-settings' %}" class="dropdown-item">{% trans "Settings" %}</a>
<a href="{% url 'babybuddy:user-password' %}" class="dropdown-item">{% trans "Password" %}</a>
<a href="{% url 'babybuddy:logout' %}" class="dropdown-item">{% trans "Logout" %}</a>
<h6 class="dropdown-header">{% trans "Site" %}</h6>
<a href="{% url 'api:api-root' %}"
class="dropdown-item"
target="_blank">API Browser</a>
target="_blank">{% trans "API Browser" %}</a>
{% if request.user.is_staff %}
<a href="{% url 'babybuddy:user-list' %}" class="dropdown-item">Users</a>
<a href="{% url 'babybuddy:user-list' %}" class="dropdown-item">{% trans "Users" %}</a>
<a href="{% url 'admin:index' %}"
class="dropdown-item"
target="_blank">Backend Admin</a>
target="_blank">{% trans "Backend Admin" %}</a>
{% endif %}
<h6 class="dropdown-header">Support</h6>
<h6 class="dropdown-header">{% trans "Support" %}</h6>
<a href="https://github.com/cdubz/babybuddy"
class="dropdown-item"
target="_blank">
<i class="icon icon-source" aria-hidden="true"></i> Source Code</a>
<i class="icon icon-source" aria-hidden="true"></i> {% trans "Source Code" %}</a>
<a href="https://gitter.im/babybuddy/Lobby"
class="dropdown-item"
target="_blank">
<i class="icon icon-chat" aria-hidden="true"></i> Chat / Support</a>
<i class="icon icon-chat" aria-hidden="true"></i> {% trans "Chat / Support" %}</a>
<h6 class="dropdown-header">v{% version_string %}</h6>
</div>
</li>

View File

@ -1,4 +1,4 @@
{% load babybuddy_tags %}
{% load i18n babybuddy_tags %}
{% if is_paginated %}
<nav aria-label="Page navigation">
@ -8,7 +8,7 @@
<li class="page-item">
<a class="page-link" href="{% relative_url 'page' page_obj.previous_page_number %}" aria-label="Previous">
<i class="icon icon-chevron-left" aria-hidden="true"></i>
<span class="sr-only">Previous</span>
<span class="sr-only">{% trans "Previous" %}</span>
</a>
</li>
{% endif %}
@ -25,7 +25,7 @@
<li class="page-item">
<a class="page-link" href="{% relative_url 'page' page_obj.next_page_number %}" aria-label="Next">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
<span class="sr-only">Next</span>
<span class="sr-only">{% trans "Next" %}</span>
</a>
</li>
{% endif %}

View File

@ -1,19 +1,19 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete User{% endblock %}
{% block title %}{% trans "Delete User" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">Users</a></li>
<li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">{% trans "Users" %}</a></li>
<li class="breadcrumb-item">{{ object }}</li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'babybuddy:user-list' %}" class="btn btn-default">Cancel</a>
<a href="{% url 'babybuddy:user-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,28 +1,29 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
Create User
{% trans "Create User" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">Users</a></li>
<li class="breadcrumb-item"><a href="{% url 'babybuddy:user-list' %}">{% trans "Users" %}</a></li>
{% if object %}
<li class="breadcrumb-item font-weight-bold">{{ object }}</li>
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Create User</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Create User" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Create User</h1>
<h1>{% trans "Create User" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,10 +1,10 @@
{% extends 'babybuddy/page.html' %}
{% load bootstrap widget_tweaks %}
{% load bootstrap i18n widget_tweaks %}
{% block title %}Users{% endblock %}
{% block title %}{% trans "Users" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Users</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Users" %}</li>
{% endblock %}
{% block content %}
@ -14,13 +14,13 @@
<table class="table table-striped table-hover user-list">
<thead class="thead-inverse">
<tr>
<th>User</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Staff</th>
<th>Active</th>
<th class="text-center">Actions</th>
<th>{% trans "User" %}</th>
<th>{% trans "First Name" %}</th>
<th>{% trans "Last Name" %}</th>
<th>{% trans "Email" %}</th>
<th>{% trans "Staff" %}</th>
<th>{% trans "Active" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -52,7 +52,7 @@
</tr>
{% empty %}
<tr>
<th colspan="4">No users found.</th>
<th colspan="4">{% trans "No users found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -62,7 +62,7 @@
{% if perms.admin.add_user %}
<a href="{% url 'babybuddy:user-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-add" aria-hidden="true"></i> Create User
<i class="icon icon-add" aria-hidden="true"></i> {% trans "Create User" %}
</a>
{% endif %}

View File

@ -1,14 +1,14 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Change Password{% endblock %}
{% block title %}{% trans "Change Password" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item">User</li>
<li class="breadcrumb-item active">Change Password</li>
<li class="breadcrumb-item">{% trans "User" %}</li>
<li class="breadcrumb-item active">{% trans "Change Password" %}</li>
{% endblock %}
{% block content %}
<h1>Change Password</h1>
<h1>{% trans "Change Password" %}</h1>
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,31 +1,31 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}User Settings{% endblock %}
{% block title %}{% trans "User Settings" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item">User</li>
<li class="breadcrumb-item active">Settings</li>
<li class="breadcrumb-item">{% trans "User" %}</li>
<li class="breadcrumb-item active">{% trans "Settings" %}</li>
{% endblock %}
{% block content %}
<h1>User Settings</h1>
<h1>{% trans "User Settings" %}</h1>
<div class="container-fluid">
<form role="form" method="post">
{% csrf_token %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> {{ error }}
{% blocktrans %}<strong>Error:</strong> {{ error }}{% endblocktrans %}
</div>
{% endfor %}
{% elif form.errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> Some fields have errors. See below for details.
{% blocktrans %}<strong>Error:</strong> Some fields have errors. See below for details.{% endblocktrans %}
</div>
{% endif %}
<fieldset>
<legend>User Profile</legend>
<legend>{% trans "User Profile" %}</legend>
<div class="form-group row">
{% with form_user.first_name as field %}
{% include 'babybuddy/form_field.html' %}
@ -41,9 +41,14 @@
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
<div class="form-group row">
{% with form_settings.language as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
</fieldset>
<fieldset>
<legend>Dashboard</legend>
<legend>{% trans "Dashboard" %}</legend>
<div class="form-group row">
{% with form_settings.dashboard_refresh_rate as field %}
{% include 'babybuddy/form_field.html' %}
@ -51,16 +56,16 @@
</div>
</fieldset>
<fieldset>
<legend>API</legend>
<legend>{% trans "API" %}</legend>
<div class="form-group row">
<label for="id_email" class="col-sm-2 col-form-label">Key</label>
<label for="id_email" class="col-sm-2 col-form-label">{% trans "Key" %}</label>
<div class="col-sm-10">
<samp>{{ user.settings.api_key }}</samp>
<a class="btn btn-xs btn-danger" href="{% url 'babybuddy:user-reset-api-key' %}">Regenerate</a>
<a class="btn btn-xs btn-danger" href="{% url 'babybuddy:user-reset-api-key' %}">{% trans "Regenerate" %}</a>
</div>
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
</form>
</div>
{% endblock %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Welcome!{% endblock %}
{% block title %}{% trans "Welcome!" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Welcome!</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Welcome!" %}</li>
{% endblock %}
{% block content %}
<div class="jumbotron">
<h1 class="display-3">Welcome to Baby Buddy!</h1>
<h1 class="display-3">{% trans "Welcome to Baby Buddy!" %}</h1>
<p class="lead">
Learn about and predict baby's needs without (<em>as much</em>)
guess work by using Baby Buddy to track &mdash;
{% blocktrans %}Learn about and predict baby's needs without
(<em>as much</em>) guess work by using Baby Buddy to track &mdash;{% endblocktrans %}
</p>
<hr class="my-4">
<div class="card-deck">
@ -21,7 +21,7 @@
<i class="icon icon-2x icon-diaperchange" aria-hidden="true"></i>
</div>
<div class="card-body">
<h3 class="card-title text-center">Diaper Changes</h3>
<h3 class="card-title text-center">{% trans "Diaper Changes" %}</h3>
</div>
</div>
<div class="card card-feeding">
@ -29,7 +29,7 @@
<i class="icon icon-2x icon-feeding" aria-hidden="true"></i>
</div>
<div class="card-body">
<h3 class="card-title text-center">Feedings</h3>
<h3 class="card-title text-center">{% trans "Feedings" %}</h3>
</div>
</div>
<div class="card card-sleep">
@ -37,7 +37,7 @@
<i class="icon icon-2x icon-sleep" aria-hidden="true"></i>
</div>
<div class="card-body">
<h3 class="card-title text-center">Sleep</h3>
<h3 class="card-title text-center">{% trans "Sleep" %}</h3>
</div>
</div>
<div class="card card-tummytime">
@ -45,23 +45,23 @@
<i class="icon icon-2x icon-tummytime" aria-hidden="true"></i>
</div>
<div class="card-body">
<h3 class="card-title text-center">Tummy Time</h3>
<h3 class="card-title text-center">{% trans "Tummy Time" %}</h3>
</div>
</div>
</div>
<hr class="my-4">
<p class="lead">
As the amount of entries grows, Baby Buddy will help
{% blocktrans %}As the amount of entries grows, Baby Buddy will help
parents and caregivers to identify small patterns in baby's habits
using the dashboard and graphs. Baby Buddy is mobile-friendly and
uses a dark theme to help weary moms and dads with 2AM feedings and
changings. To get started, just click the button below to add your
first (or second, third, etc.) child!
first (or second, third, etc.) child!{% endblocktrans %}
</p>
<p class="text-center">
{% if perms.core.add_child %}
<a href="{% url 'core:child-add' %}" class="btn btn-lg btn-success">
<i class="icon icon-child" aria-hidden="true"></i> Add a Child
<i class="icon icon-child" aria-hidden="true"></i> {% trans "Add a Child" %}
</a>
{% endif %}
</p>

View File

@ -1,5 +1,5 @@
{% extends "registration/base.html" %}
{% load static widget_tweaks %}
{% load i18n static widget_tweaks %}
{% block title %}Login{% endblock %}
{% block content %}
@ -29,12 +29,12 @@
</div>
<button class="btn btn-primary w-100 fade-in" type="submit" name="login">
Login
{% trans "Login" %}
</button>
</form>
<div class="bg-faded text-center px-4 py-3 rounded-bottom">
<a href="{% url 'babybuddy:password_reset' %}" name="reset">
Forgot your password?</a>
{% trans "Forgot your password?" %}</a>
</div>
{% endblock %}

View File

@ -1,11 +1,12 @@
{% extends "registration/base.html" %}
{% load i18n %}
{% block title %}Password Reset Successfully!{% endblock %}
{% block title %}{% trans "Password Reset Successfully!" %}{% endblock %}
{% block content %}
<div class="text-center mb-0">
<p>Your password has been set. You may go ahead and log in now.</p>
<p class="mb-0"><a href="{{ login_url }}">Log in</a></p>
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
<p class="mb-0"><a href="{{ login_url }}">{% trans "Log in" %}</a></p>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "registration/base.html" %}
{% load static widget_tweaks %}
{% load i18n static widget_tweaks %}
{% block title %}Password Reset{% endblock %}
{% block title %}{% trans "Password Reset" %}{% endblock %}
{% block content %}
<form method="post">
@ -9,13 +9,13 @@
{% if form.errors %}
<div class="alert alert-danger">
<p class="mb-0"><strong>Oh snap!</strong> The two passwords
did not match. Please try again.</p>
{% blocktrans %}<p class="mb-0"><strong>Oh snap!</strong> The
two passwords did not match. Please try again.</p>{% endblocktrans %}
</div>
{% endif %}
<div class="text-center mb-4">
<p class="mb-0">Enter your new password in each field below.</p>
<p class="mb-0">{% trans "Enter your new password in each field below." %}</p>
</div>
<label class="sr-only" for="password1-input-group">
@ -28,7 +28,6 @@
{% render_field form.new_password1 name='new_password1' class+='form-control' id='password1-input-group' %}
</div>
<label class="sr-only" for="password2-input-group">
{{ form.new_password2.label }}
</label>
@ -40,7 +39,7 @@
</div>
<button class="btn btn-primary w-100 fade-in" type="submit" name="reset">
Reset Password
{% trans "Reset Password" %}
</button>
</form>

View File

@ -1,15 +1,16 @@
{% extends "registration/base.html" %}
{% load i18n %}
{% block title %}Reset Email Sent{% endblock %}
{% block title %}{% trans "Reset Email Sent" %}{% endblock %}
{% block content %}
<div class="text-center mb-0">
<p>We've emailed you instructions for setting your
{% blocktrans %}<p>We've emailed you instructions for setting your
password, if an account exists with the email you entered. You
should receive them shortly.</p>
<p class="mb-0">If you don't receive an email, please make sure you've
entered the address you registered with, and check your spam
folder.</p>
folder.</p>{% endblocktrans %}
</div>
{% endblock %}

View File

@ -1,13 +1,13 @@
{% extends "registration/base.html" %}
{% load static widget_tweaks %}
{% load i18n static widget_tweaks %}
{% block title %}Forgot Password{% endblock %}
{% block title %}{% trans "Forgot Password" %}{% endblock %}
{% block content %}
<div class="text-center mb-4">
<p class="mb-0">Enter your account email address in the form below. If
the address is valid, you will receive instructions for resetting your
password.</p>
{% blocktrans %}<p class="mb-0">Enter your account email address in the
form below. If the address is valid, you will receive instructions for
resetting your password.</p>{% endblocktrans %}
</div>
<form method="post">
@ -24,7 +24,7 @@
</div>
<button class="btn btn-primary w-100 fade-in" type="submit" name="reset">
Reset Password
{% trans "Reset Password" %}
</button>
</form>

View File

@ -25,6 +25,14 @@ class FormsTestCase(TestCase):
cls.user = User.objects.create_user(
is_superuser=True, **cls.credentials)
cls.settings_template = {
'first_name': 'User',
'last_name': 'Name',
'email': 'user@user.user',
'dashboard_refresh_rate': '',
'language': 'en'
}
def test_change_password(self):
self.c.login(**self.credentials)
@ -85,18 +93,28 @@ class FormsTestCase(TestCase):
def test_user_settings(self):
self.c.login(**self.credentials)
params = {
'first_name': 'User',
'last_name': 'Name',
'email': 'user@user.user',
'dashboard_refresh_rate': ''
}
params = self.settings_template.copy()
params['first_name'] = 'New First Name'
page = self.c.post('/user/settings/', params)
self.assertEqual(page.status_code, 302)
page = self.c.post('/user/settings/', params, follow=True)
self.assertEqual(page.status_code, 200)
self.assertContains(page, 'New First Name')
def test_user_settings_invalid(self):
self.c.login(**self.credentials)
params = self.settings_template.copy()
params['email'] = 'Not an email address'
params = {'email': 'Not an email address'}
page = self.c.post('/user/settings/', params)
self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'user_form', 'email',
'Enter a valid email address.')
def test_user_settings_language(self):
self.c.login(**self.credentials)
params = self.settings_template.copy()
params['language'] = 'fr'
page = self.c.post('/user/settings/', data=params, follow=True)
self.assertContains(page, 'Paramètres Utilisateur')

View File

@ -17,10 +17,13 @@ class SettingsTestCase(TestCase):
}
user = User.objects.create_user(is_superuser=True, **credentials)
self.assertIsInstance(user.settings, Settings)
self.assertEqual(str(user.settings), 'Test\'s Settings')
self.assertEqual(
user.settings.dashboard_refresh_rate_milliseconds, 60000)
user.settings.dashboard_refresh_rate = None
user.save()
self.assertIsNone(user.settings.dashboard_refresh_rate_milliseconds)
user.settings.language = 'fr'
user.save()
self.assertEqual(user.settings.language, 'fr')

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
from django.test import Client as HttpClient
from django.contrib.auth.models import User
from django.core.management import call_command
from faker import Factory
class TranslationTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(TranslationTestCase, cls).setUpClass()
fake = Factory.create()
call_command('migrate', verbosity=0)
cls.c = HttpClient()
fake_user = fake.simple_profile()
cls.credentials = {
'username': fake_user['username'],
'password': fake.password()
}
cls.user = User.objects.create_user(
is_superuser=True, **cls.credentials)
cls.params_template = {
'first_name': 'User',
'last_name': 'Name',
'email': 'user@user.user',
'dashboard_refresh_rate': '',
'language': 'en'
}
cls.c.login(**cls.credentials)
def test_translation_fr(self):
params = self.params_template.copy()
params['language'] = 'fr'
page = self.c.post('/user/settings/', data=params, follow=True)
self.assertContains(page, 'Paramètres Utilisateur')
page = self.c.get('/welcome/')
self.assertContains(page, 'Bienvenue à Baby Buddy!')

View File

@ -53,6 +53,7 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('', include('api.urls', namespace='api')),
path('', include((app_patterns, 'babybuddy'), namespace='babybuddy')),
path('user/lang', include('django.conf.urls.i18n')),
path('', include('core.urls', namespace='core')),
path('', include('dashboard.urls', namespace='dashboard')),
path('', include('reports.urls', namespace='reports')),

View File

@ -7,9 +7,12 @@ from django.contrib.auth.models import User
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.text import format_lazy
from django.utils.translation import gettext as _, gettext_lazy
from django.views.generic import View
from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.i18n import set_language
from django_filters.views import FilterView
@ -50,7 +53,7 @@ class UserAdd(StaffOnlyMixin, PermissionRequired403Mixin, SuccessMessageMixin,
permission_required = ('admin.add_user',)
form_class = forms.UserAddForm
success_url = reverse_lazy('babybuddy:user-list')
success_message = 'User %(username)s added!'
success_message = gettext_lazy('User %(username)s added!')
class UserUpdate(StaffOnlyMixin, PermissionRequired403Mixin,
@ -60,7 +63,7 @@ class UserUpdate(StaffOnlyMixin, PermissionRequired403Mixin,
permission_required = ('admin.change_user',)
form_class = forms.UserUpdateForm
success_url = reverse_lazy('babybuddy:user-list')
success_message = 'User %(username)s updated.'
success_message = gettext_lazy('User %(username)s updated.')
class UserDelete(StaffOnlyMixin, PermissionRequired403Mixin,
@ -71,7 +74,9 @@ class UserDelete(StaffOnlyMixin, PermissionRequired403Mixin,
success_url = reverse_lazy('babybuddy:user-list')
def delete(self, request, *args, **kwargs):
success_message = 'User {} deleted.'.format(self.get_object())
success_message = format_lazy(gettext_lazy(
'User {user} deleted.'), user=self.get_object()
)
messages.success(request, success_message)
return super(UserDelete, self).delete(request, *args, **kwargs)
@ -93,7 +98,7 @@ class UserPassword(LoginRequiredMixin, View):
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
messages.success(request, 'Password updated.')
messages.success(request, _('Password updated.'))
return render(request, self.template_name, {'form': form})
@ -103,7 +108,7 @@ class UserResetAPIKey(LoginRequiredMixin, View):
"""
def get(self, request):
request.user.settings.api_key(reset=True)
messages.success(request, 'User API key regenerated.')
messages.success(request, _('User API key regenerated.'))
return redirect('babybuddy:user-settings')
@ -135,7 +140,8 @@ class UserSettings(LoginRequiredMixin, View):
user_settings = form_settings.save(commit=False)
user.settings = user_settings
user.save()
messages.success(request, 'Settings saved!')
set_language(request)
messages.success(request, _('Settings saved!'))
return redirect('babybuddy:user-settings')
return render(request, self.template_name, {
'user_form': form_user,

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from django import forms
from django.utils import timezone
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext as _
from core import models
@ -77,7 +78,7 @@ class ChildDeleteForm(forms.ModelForm):
confirm_name = self.cleaned_data['confirm_name']
if confirm_name != str(self.instance):
raise forms.ValidationError(
'Name does not match child name.', code='confirm_mismatch')
_('Name does not match child name.'), code='confirm_mismatch')
return confirm_name
def save(self, commit=True):

View File

@ -0,0 +1,238 @@
# Generated by Django 2.2 on 2019-04-17 03:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('core', '0004_child_picture'),
]
operations = [
migrations.AlterModelOptions(
name='child',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['last_name', 'first_name'], 'verbose_name': 'Child', 'verbose_name_plural': 'Children'},
),
migrations.AlterModelOptions(
name='diaperchange',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-time'], 'verbose_name': 'Diaper Change', 'verbose_name_plural': 'Diaper Changes'},
),
migrations.AlterModelOptions(
name='feeding',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Feeding', 'verbose_name_plural': 'Feedings'},
),
migrations.AlterModelOptions(
name='note',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-time'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'},
),
migrations.AlterModelOptions(
name='sleep',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Sleep', 'verbose_name_plural': 'Sleep'},
),
migrations.AlterModelOptions(
name='timer',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-active', '-start', '-end'], 'verbose_name': 'Timer', 'verbose_name_plural': 'Timers'},
),
migrations.AlterModelOptions(
name='tummytime',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Tummy Time', 'verbose_name_plural': 'Tummy Time'},
),
migrations.AlterModelOptions(
name='weight',
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-date'], 'verbose_name': 'Weight', 'verbose_name_plural': 'Weight'},
),
migrations.AlterField(
model_name='child',
name='birth_date',
field=models.DateField(verbose_name='Birth date'),
),
migrations.AlterField(
model_name='child',
name='first_name',
field=models.CharField(max_length=255, verbose_name='First name'),
),
migrations.AlterField(
model_name='child',
name='last_name',
field=models.CharField(max_length=255, verbose_name='Last name'),
),
migrations.AlterField(
model_name='child',
name='picture',
field=models.ImageField(blank=True, null=True, upload_to='child/picture/', verbose_name='Picture'),
),
migrations.AlterField(
model_name='child',
name='slug',
field=models.SlugField(editable=False, max_length=100, unique=True, verbose_name='Slug'),
),
migrations.AlterField(
model_name='diaperchange',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='diaper_change', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='diaperchange',
name='color',
field=models.CharField(blank=True, choices=[('black', 'Black'), ('brown', 'Brown'), ('green', 'Green'), ('yellow', 'Yellow')], max_length=255, verbose_name='Color'),
),
migrations.AlterField(
model_name='diaperchange',
name='solid',
field=models.BooleanField(verbose_name='Solid'),
),
migrations.AlterField(
model_name='diaperchange',
name='time',
field=models.DateTimeField(verbose_name='Time'),
),
migrations.AlterField(
model_name='diaperchange',
name='wet',
field=models.BooleanField(verbose_name='Wet'),
),
migrations.AlterField(
model_name='feeding',
name='amount',
field=models.FloatField(blank=True, null=True, verbose_name='Amount'),
),
migrations.AlterField(
model_name='feeding',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeding', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='feeding',
name='duration',
field=models.DurationField(editable=False, null=True, verbose_name='Duration'),
),
migrations.AlterField(
model_name='feeding',
name='end',
field=models.DateTimeField(verbose_name='End time'),
),
migrations.AlterField(
model_name='feeding',
name='method',
field=models.CharField(choices=[('bottle', 'Bottle'), ('left breast', 'Left breast'), ('right breast', 'Right breast')], max_length=255, verbose_name='Method'),
),
migrations.AlterField(
model_name='feeding',
name='start',
field=models.DateTimeField(verbose_name='Start time'),
),
migrations.AlterField(
model_name='feeding',
name='type',
field=models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula')], max_length=255, verbose_name='Type'),
),
migrations.AlterField(
model_name='note',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='note', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='note',
name='note',
field=models.TextField(verbose_name='Note'),
),
migrations.AlterField(
model_name='note',
name='time',
field=models.DateTimeField(auto_now=True, verbose_name='Time'),
),
migrations.AlterField(
model_name='sleep',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sleep', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='sleep',
name='duration',
field=models.DurationField(editable=False, null=True, verbose_name='Duration'),
),
migrations.AlterField(
model_name='sleep',
name='end',
field=models.DateTimeField(verbose_name='End time'),
),
migrations.AlterField(
model_name='sleep',
name='start',
field=models.DateTimeField(verbose_name='Start time'),
),
migrations.AlterField(
model_name='timer',
name='active',
field=models.BooleanField(default=True, editable=False, verbose_name='Active'),
),
migrations.AlterField(
model_name='timer',
name='duration',
field=models.DurationField(editable=False, null=True, verbose_name='Duration'),
),
migrations.AlterField(
model_name='timer',
name='end',
field=models.DateTimeField(blank=True, editable=False, null=True, verbose_name='End time'),
),
migrations.AlterField(
model_name='timer',
name='name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='timer',
name='start',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Start time'),
),
migrations.AlterField(
model_name='timer',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='timers', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='tummytime',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tummy_time', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='tummytime',
name='duration',
field=models.DurationField(editable=False, null=True, verbose_name='Duration'),
),
migrations.AlterField(
model_name='tummytime',
name='end',
field=models.DateTimeField(verbose_name='End time'),
),
migrations.AlterField(
model_name='tummytime',
name='milestone',
field=models.CharField(blank=True, max_length=255, verbose_name='Milestone'),
),
migrations.AlterField(
model_name='tummytime',
name='start',
field=models.DateTimeField(verbose_name='Start time'),
),
migrations.AlterField(
model_name='weight',
name='child',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='weight', to='core.Child', verbose_name='Child'),
),
migrations.AlterField(
model_name='weight',
name='date',
field=models.DateField(verbose_name='Date'),
),
migrations.AlterField(
model_name='weight',
name='weight',
field=models.FloatField(verbose_name='Weight'),
),
]

View File

@ -6,6 +6,8 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.template.defaultfilters import slugify
from django.utils import timezone
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
def validate_date(date, field_name):
@ -17,7 +19,7 @@ def validate_date(date, field_name):
"""
if date and date > timezone.localdate():
raise ValidationError(
{field_name: 'Date can not be in the future.'},
{field_name: _('Date can not be in the future.')},
code='date_invalid')
@ -31,10 +33,10 @@ def validate_duration(model, max_duration=timedelta(hours=24)):
if model.start and model.end:
if model.start > model.end:
raise ValidationError(
'Start time must come before end time.',
_('Start time must come before end time.'),
code='end_before_start')
if model.end - model.start > max_duration:
raise ValidationError('Duration too long.', code='max_duration')
raise ValidationError(_('Duration too long.'), code='max_duration')
def validate_unique_period(queryset, model):
@ -50,7 +52,7 @@ def validate_unique_period(queryset, model):
if model.start and model.end:
if queryset.filter(start__lte=model.end, end__gte=model.start):
raise ValidationError(
'Another entry intersects the specified time period.',
_('Another entry intersects the specified time period.'),
code='period_intersection')
@ -63,20 +65,30 @@ def validate_time(time, field_name):
"""
if time and time > timezone.localtime():
raise ValidationError(
{field_name: 'Date/time can not be in the future.'},
{field_name: _('Date/time can not be in the future.')},
code='time_invalid')
class Child(models.Model):
model_name = 'child'
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
birth_date = models.DateField(blank=False, null=False)
slug = models.SlugField(max_length=100, unique=True, editable=False)
first_name = models.CharField(max_length=255, verbose_name=_('First name'))
last_name = models.CharField(max_length=255, verbose_name=_('Last name'))
birth_date = models.DateField(
blank=False,
null=False,
verbose_name=_('Birth date')
)
slug = models.SlugField(
editable=False,
max_length=100,
unique=True,
verbose_name=_('Slug')
)
picture = models.ImageField(
upload_to='child/picture/',
blank=True,
null=True
null=True,
upload_to='child/picture/',
verbose_name=_('Picture')
)
objects = models.Manager()
@ -84,7 +96,8 @@ class Child(models.Model):
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['last_name', 'first_name']
verbose_name_plural = 'Children'
verbose_name = _('Child')
verbose_name_plural = _('Children')
def __str__(self):
return '{} {}'.format(self.first_name, self.last_name)
@ -103,25 +116,40 @@ class Child(models.Model):
class DiaperChange(models.Model):
model_name = 'diaperchange'
child = models.ForeignKey(
'Child', related_name='diaper_change', on_delete=models.CASCADE)
time = models.DateTimeField(blank=False, null=False)
wet = models.BooleanField()
solid = models.BooleanField()
color = models.CharField(max_length=255, blank=True, choices=[
('black', 'Black'),
('brown', 'Brown'),
('green', 'Green'),
('yellow', 'Yellow'),
])
'Child',
on_delete=models.CASCADE,
related_name='diaper_change',
verbose_name=_('Child')
)
time = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Time')
)
wet = models.BooleanField(verbose_name=_('Wet'))
solid = models.BooleanField(verbose_name=_('Solid'))
color = models.CharField(
blank=True,
choices=[
('black', _('Black')),
('brown', _('Brown')),
('green', _('Green')),
('yellow', _('Yellow')),
],
max_length=255,
verbose_name=_('Color')
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-time']
verbose_name = _('Diaper Change')
verbose_name_plural = _('Diaper Changes')
def __str__(self):
return 'Diaper Change'
return str(_('Diaper Change'))
def attributes(self):
attributes = []
@ -139,35 +167,58 @@ class DiaperChange(models.Model):
# One or both of Wet and Solid is required.
if not self.wet and not self.solid:
raise ValidationError(
'Wet and/or solid is required.', code='wet_or_solid')
_('Wet and/or solid is required.'), code='wet_or_solid')
class Feeding(models.Model):
model_name = 'feeding'
child = models.ForeignKey(
'Child', related_name='feeding', on_delete=models.CASCADE)
start = models.DateTimeField(blank=False, null=False)
end = models.DateTimeField(blank=False, null=False)
duration = models.DurationField(null=True, editable=False)
type = models.CharField(max_length=255, choices=[
('breast milk', 'Breast milk'),
('formula', 'Formula'),
])
method = models.CharField(max_length=255, choices=[
('bottle', 'Bottle'),
('left breast', 'Left breast'),
('right breast', 'Right breast'),
])
amount = models.FloatField(blank=True, null=True)
'Child',
on_delete=models.CASCADE,
related_name='feeding',
verbose_name=_('Child')
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
)
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
)
type = models.CharField(
choices=[('breast milk', _('Breast milk')), ('formula', _('Formula'))],
max_length=255,
verbose_name=_('Type')
)
method = models.CharField(
choices=[
('bottle', _('Bottle')),
('left breast', _('Left breast')),
('right breast', _('Right breast')),
],
max_length=255,
verbose_name=_('Method')
)
amount = models.FloatField(blank=True, null=True, verbose_name=_('Amount'))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name = _('Feeding')
verbose_name_plural = _('Feedings')
def __str__(self):
return 'Feeding'
return str(_('Feeding'))
def save(self, *args, **kwargs):
if self.start and self.end:
@ -184,25 +235,31 @@ class Feeding(models.Model):
if self.type == 'formula'and self.method != 'bottle':
raise ValidationError(
{'method':
'Only "Bottle" method is allowed with "Formula" type.'},
_('Only "Bottle" method is allowed with "Formula" type.')},
code='bottle_formula_mismatch')
class Note(models.Model):
model_name = 'note'
child = models.ForeignKey(
'Child', related_name='note', on_delete=models.CASCADE)
note = models.TextField()
time = models.DateTimeField(auto_now=True)
'Child',
on_delete=models.CASCADE,
related_name='note',
verbose_name=_('Child')
)
note = models.TextField(verbose_name=_('Note'))
time = models.DateTimeField(auto_now=True, verbose_name=_('Time'))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-time']
verbose_name = _('Note')
verbose_name_plural = _('Notes')
def __str__(self):
return 'Note'
return str(_('Note'))
class NapsManager(models.Manager):
@ -214,10 +271,26 @@ class NapsManager(models.Manager):
class Sleep(models.Model):
model_name = 'sleep'
child = models.ForeignKey(
'Child', related_name='sleep', on_delete=models.CASCADE)
start = models.DateTimeField(blank=False, null=False)
end = models.DateTimeField(blank=False, null=False)
duration = models.DurationField(null=True, editable=False)
'Child',
on_delete=models.CASCADE,
related_name='sleep',
verbose_name=_('Child')
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
)
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
)
objects = models.Manager()
naps = NapsManager()
@ -225,10 +298,11 @@ class Sleep(models.Model):
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name_plural = 'Sleep'
verbose_name = _('Sleep')
verbose_name_plural = _('Sleep')
def __str__(self):
return 'Sleep'
return str(_('Sleep'))
@property
def nap(self):
@ -253,26 +327,50 @@ class Sleep(models.Model):
class Timer(models.Model):
model_name = 'timer'
name = models.CharField(max_length=255, null=True, blank=True)
name = models.CharField(
blank=True,
max_length=255,
null=True,
verbose_name=_('Name')
)
start = models.DateTimeField(
default=timezone.now,
blank=False,
verbose_name='Start Time'
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=True,
editable=False,
null=True,
verbose_name=_('End time')
)
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
)
active = models.BooleanField(
default=True,
editable=False,
verbose_name=_('Active')
)
end = models.DateTimeField(blank=True, null=True, editable=False)
duration = models.DurationField(null=True, editable=False)
active = models.BooleanField(default=True, editable=False)
user = models.ForeignKey(
'auth.User', related_name='timers', on_delete=models.CASCADE)
'auth.User',
on_delete=models.CASCADE,
related_name='timers',
verbose_name=_('User')
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-active', '-start', '-end']
verbose_name = _('Timer')
verbose_name_plural = _('Timers')
def __str__(self):
return self.name or 'Timer #{}'.format(self.id)
return self.name or str(format_lazy(_('Timer #{id}'), id=self.id))
@classmethod
def from_db(cls, db, field_names, values):
@ -315,20 +413,42 @@ class Timer(models.Model):
class TummyTime(models.Model):
model_name = 'tummytime'
child = models.ForeignKey(
'Child', related_name='tummy_time', on_delete=models.CASCADE)
start = models.DateTimeField(blank=False, null=False)
end = models.DateTimeField(blank=False, null=False)
duration = models.DurationField(null=True, editable=False)
milestone = models.CharField(max_length=255, blank=True)
'Child',
on_delete=models.CASCADE,
related_name='tummy_time',
verbose_name=_('Child')
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
)
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
)
milestone = models.CharField(
blank=True,
max_length=255,
verbose_name=_('Milestone')
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name = _('Tummy Time')
verbose_name_plural = _('Tummy Time')
def __str__(self):
return 'Tummy Time'
return str(_('Tummy Time'))
def save(self, *args, **kwargs):
if self.start and self.end:
@ -346,19 +466,32 @@ class TummyTime(models.Model):
class Weight(models.Model):
model_name = 'weight'
child = models.ForeignKey(
'Child', related_name='weight', on_delete=models.CASCADE)
weight = models.FloatField(blank=False, null=False)
date = models.DateField(blank=False, null=False)
'Child',
on_delete=models.CASCADE,
related_name='weight',
verbose_name=_('Child')
)
weight = models.FloatField(
blank=False,
null=False,
verbose_name=_('Weight')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name_plural = 'Weight'
verbose_name = _('Weight')
verbose_name_plural = _('Weight')
def __str__(self):
return 'Weight'
return str(_('Weight'))
def clean(self):
validate_date(self.date, 'date')

View File

@ -1,20 +1,22 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Child{% endblock %}
{% block title %}{% trans "Delete a Child" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">Children</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<div class="form-group">
<label for="{{ form.confirm_name.id_for_label }}">To confirm this action. Type the full name of the child below.</label>
<label for="{{ form.confirm_name.id_for_label }}">
{% trans "To confirm this action. Type the full name of the child below." %}
</label>
{% if form.confirm_name.errors %}
{{ form.confirm_name|add_class:"form-control is-invalid" }}
{% else %}
@ -24,7 +26,7 @@
<div class="invalid-feedback">{{ form.confirm_name.errors.0 }}</div>
{% endif %}
</div>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:child-list' %}" class="btn btn-default">Cancel</a>
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:child-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,10 +1,10 @@
{% extends 'babybuddy/page.html' %}
{% load static thumbnail %}
{% load i18n static thumbnail %}
{% block title %}{{ object }}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">Children</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold">{{ object }}</li>
{% endblock %}
@ -20,8 +20,8 @@
{% endif %}
<div class="child-name display-4">{{ object }}</div>
<p class="lead">
Born <span class="text-secondary">{{ object.birth_date }}</span><br/>
Age <span class="text-secondary">{{ object.birth_date|timesince }}</span>
{% trans "Born" %} <span class="text-secondary">{{ object.birth_date }}</span><br/>
{% trans "Age" %} <span class="text-secondary">{{ object.birth_date|timesince }}</span>
</p>
{% include 'dashboard/child_button_group.html' %}
</div>
@ -31,16 +31,16 @@
<div class="col-lg-8 offset-lg-4 col-md-6 offset-md-6">
<h3 class="text-center">
{% if date_previous %}
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="Previous">
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="{% trans "Previous" %}">
<i class="icon icon-chevron-left" aria-hidden="true"></i>
<span class="sr-only">Previous</span>
<span class="sr-only">{% trans "Previous" %}</span>
</a>
{% endif %}
{{ date|date }}
{% if date_next %}
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="Next">
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="{% trans "Next" %}">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
<span class="sr-only">Next</span>
<span class="sr-only">{% trans "Next" %}</span>
</a>
{% endif %}
</h3>
@ -55,7 +55,7 @@
{{ object.event }}
</div>
<div class="card-footer text-muted">
{{ object.time|timesince }} ago ({{ object.time|time }})
{% blocktrans with since=object.time|timesince time=object.time|time %}{{ since }} ago ({{ time }}){% endblocktrans %}
</div>
</div>
</li>

View File

@ -1,28 +1,29 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
Add a Child
{% trans "Add a Child" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">Children</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
{% if object %}
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add a Child</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a Child" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Child</h1>
<h1>{% trans "Add a Child" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,24 +1,24 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks static thumbnail %}
{% load i18n static thumbnail widget_tweaks %}
{% block title %}Children{% endblock %}
{% block title %}{% trans "Children" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Children</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Children" %}</li>
{% endblock %}
{% block content %}
<h1>Children</h1>
<h1>{% trans "Children" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover child-list">
<thead class="thead-inverse">
<tr>
<th class="picture-column"><i class="icon icon-camera" aria-hidden="true"></i></th>
<th>First Name</th>
<th>Last Name</th>
<th>Birth Date</th>
<th class="text-center">Actions</th>
<th>{% trans "First Name" %}</th>
<th>{% trans "Last Name" %}</th>
<th>{% trans "Birth Date" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -38,7 +38,7 @@
<td>{{ child.last_name }}</td>
<td>{{ child.birth_date }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_child %}
<a href="{% url 'core:child-update' child.slug %}" class="btn btn-warning">
@ -57,7 +57,7 @@
</tr>
{% empty %}
<tr>
<th colspan="4">No children found.</th>
<th colspan="4">{% trans "No children found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -67,7 +67,7 @@
{% if perms.core.add_child %}
<a href="{% url 'core:child-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-child" aria-hidden="true"></i> Add a Child
<i class="icon icon-child" aria-hidden="true"></i> {% trans "Add a Child" %}
</a>
{% endif %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Diaper Change{% endblock %}
{% block title %}{% trans "Delete a Diaper Change" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:diaperchange-list' %}">Diaper Changes</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:diaperchange-list' %}">{% trans "Diaper Changes" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:diaperchange-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:diaperchange-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if request.resolver_match.url_name == 'diaperchange-update' %}
Update a Diaper Change
{% trans "Update a Diaper Change" %}
{% else %}
Add a Diaper Change
{% trans "Add a Diaper Change" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:diaperchange-list' %}">Diaper Changes</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:diaperchange-list' %}">{% trans "Diaper Changes" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Diaper Change</h1>
<h1>{% trans "Add a Diaper Change" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,26 +1,25 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load bootstrap %}
{% load bootstrap i18n widget_tweaks %}
{% block title %}Diaper Changes{% endblock %}
{% block title %}{% trans "Diaper Changes" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Diaper Changes</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Diaper Changes" %}</li>
{% endblock %}
{% block content %}
<h1>Diaper Changes</h1>
<h1>{% trans "Diaper Changes" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th class="text-center">Wet</th>
<th class="text-center">Solid</th>
<th>Color</th>
<th>Time</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th class="text-center">{% trans "Wet" %}</th>
<th class="text-center">{% trans "Solid" %}</th>
<th>{% trans "Color" %}</th>
<th>{% trans "Time" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -32,7 +31,7 @@
<td>{{ change.color }}</td>
<td>{{ change.time|date:'n/j/y G:i' }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_diaperchange %}
<a href="{% url 'core:diaperchange-update' change.id %}" class="btn btn-primary">
@ -51,7 +50,7 @@
</tr>
{% empty %}
<tr>
<th colspan="6">No diaper changes found.</th>
<th colspan="6">{% trans "No diaper changes found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -61,7 +60,7 @@
{% if perms.core.add_diaperchange %}
<a href="{% url 'core:diaperchange-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-diaperchange" aria-hidden="true"></i> Add a Change
<i class="icon icon-diaperchange" aria-hidden="true"></i> {% trans "Add a Change" %}
</a>
{% endif %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Feeding{% endblock %}
{% block title %}{% trans "Delete a Feeding" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:feeding-list' %}">Feedings</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:feeding-list' %}">{% trans "Feedings" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:feeding-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:feeding-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if request.resolver_match.url_name == 'feeding-update' %}
Update a Feeding
{% trans "Update a Feeding" %}
{% else %}
Add a Feeding
{% trans "Add a Feeding" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:feeding-list' %}">Feedings</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:feeding-list' %}">{% trans "Feedings" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Feeding</h1>
<h1>{% trans "Add a Feeding" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,27 +1,27 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load duration %}
{% load duration i18n widget_tweaks %}
{% block title %}Feedings{% endblock %}
{% block title %}{% trans "Feedings" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Feedings</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Feedings" %}</li>
{% endblock %}
{% block content %}
<h1>Feedings</h1>
<h1>{% trans "Feedings" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th>Method</th>
<th>Type</th>
<th>Amt.</th>
<th>Duration</th>
<th>Date</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "Type" %}</th>
{% comment %}Abbreviation of "Amount"{% endcomment %}
<th>{% trans "Amt." %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Date" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -38,7 +38,7 @@
<td>{{ feeding.duration|duration_string }}</td>
<td>{{ feeding.start|date:'n/j/y G:i' }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_feeding %}
<a href="{% url 'core:feeding-update' feeding.id %}" class="btn btn-primary">
@ -57,7 +57,7 @@
</tr>
{% empty %}
<tr>
<th colspan="7">No feedings found.</th>
<th colspan="7">{% trans "No feedings found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -67,7 +67,7 @@
{% if perms.core.add_feeding %}
<a href="{% url 'core:feeding-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-feeding" aria-hidden="true"></i> Add a Feeding
<i class="icon icon-feeding" aria-hidden="true"></i> {% trans "Add a Feeding" %}
</a>
{% endif %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Note{% endblock %}
{% block title %}{% trans "Delete a Note" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:note-list' %}">Notes</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:note-list' %}">{% trans "Notes" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:note-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:note-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if request.resolver_match.url_name == 'note-update' %}
Update a Note
{% trans "Update a Note" %}
{% else %}
Add a Note
{% trans "Add a Note" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:note-list' %}">Notes</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:note-list' %}">{% trans "Notes" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Note</h1>
<h1>{% trans "Add a Note" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,23 +1,23 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Notes{% endblock %}
{% block title %}{% trans "Notes" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Notes</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Notes" %}</li>
{% endblock %}
{% block content %}
<h1>Notes</h1>
<h1>{% trans "Notes" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th>Note</th>
<th>Time</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th>{% trans "Note" %}</th>
<th>{% trans "Time" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -27,7 +27,7 @@
<td>{{ note.note }}</td>
<td>{{ note.time }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_note %}
<a href="{% url 'core:note-update' note.id %}" class="btn btn-primary">
@ -46,7 +46,7 @@
</tr>
{% empty %}
<tr>
<th colspan="4">No notes found.</th>
<th colspan="4">{% trans "No notes found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -56,7 +56,7 @@
{% if perms.core.add_note %}
<a href="{% url 'core:note-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-note" aria-hidden="true"></i> Add a Note
<i class="icon icon-note" aria-hidden="true"></i> {% trans "Add a Note" %}
</a>
{% endif %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Sleep Entry{% endblock %}
{% block title %}{% trans "Delete a Sleep Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:sleep-list' %}">Sleep</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:sleep-list' %}">{% trans "Sleep" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:sleep-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:sleep-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if request.resolver_match.url_name == 'sleep-update' %}
Update a Sleep Entry
{% trans "Update a Sleep Entry" %}
{% else %}
Add a Sleep Entry
{% trans "Add a Sleep Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:sleep-list' %}">Sleep</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:sleep-list' %}">{% trans "Sleep" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Sleep Entry</h1>
<h1>{% trans "Add a Sleep Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,25 +1,25 @@
{% extends 'babybuddy/page.html' %}
{% load bootstrap duration widget_tweaks %}
{% load bootstrap duration i18n widget_tweaks %}
{% block title %}Sleep{% endblock %}
{% block title %}{% trans "Sleep" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Sleep</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Sleep" %}</li>
{% endblock %}
{% block content %}
<h1>Sleep</h1>
<h1>{% trans "Sleep" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th>Duration</th>
<th>Start</th>
<th>End</th>
<th class="text-center">Nap</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Start" %}</th>
<th>{% trans "End" %}</th>
<th class="text-center">{% trans "Nap" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -31,7 +31,7 @@
<td>{{ sleep.end|date:'n/j/y G:i' }}</td>
<td class="text-center">{{ sleep.nap|bool_icon }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_sleep %}
<a href="{% url 'core:sleep-update' sleep.id %}" class="btn btn-primary">
@ -50,7 +50,7 @@
</tr>
{% empty %}
<tr>
<th colspan="5">No sleep entries found.</th>
<th colspan="5">{% trans "No sleep entries found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -60,7 +60,7 @@
{% if perms.core.add_sleep %}
<a href="{% url 'core:sleep-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-sleep" aria-hidden="true"></i> Add a Sleep Entry
<i class="icon icon-sleep" aria-hidden="true"></i> {% trans "Add a Sleep Entry" %}
</a>
{% endif %}

View File

@ -1,19 +1,19 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete {{ object }}{% endblock %}
{% block title %}{% blocktrans %}Delete {{ object }}{% endblocktrans %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">Timers</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="/" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="/" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,10 +1,10 @@
{% extends 'babybuddy/page.html' %}
{% load duration timers %}
{% load duration i18n timers %}
{% block title %}{{ object }}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">Timers</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
<li class="breadcrumb-item font-weight-bold">{{ object }}</li>
{% endblock %}
@ -17,34 +17,40 @@
<span class="timer-seconds">{{ object.duration|seconds }}</span>s
</div>
<p class="lead text-secondary">
Started {{ object.start }}
{% trans "Started" %} {{ object.start }}
{% if not object.active %}
/ Stopped {{ object.end }}
/ {% trans "Stopped" %} {{ object.end }}
{% endif %}
</p>
<p class="text-muted">
{{ timer }} created by {{ object.user }}
{% blocktrans %}{{ timer }} created by {{ object.user }}{% endblocktrans %}
</p>
{% if perms.core.add_feeding %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% url 'core:feeding-add' %}?timer={{ timer.id }}"
role="button"><i class="icon icon-feeding" aria-hidden="true"></i> Feeding</a>
role="button"><i class="icon icon-feeding" aria-hidden="true"></i>
{% trans "Feeding" %}
</a>
{% endif %}
{% if perms.core.add_sleep %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% url 'core:sleep-add' %}?timer={{ timer.id }}"
role="button"><i class="icon icon-sleep" aria-hidden="true"></i> Sleep</a>
role="button"><i class="icon icon-sleep" aria-hidden="true"></i>
{% trans "Sleep" %}
</a>
{% endif %}
{% if perms.core.add_tummytime %}
<a class="btn btn-success btn-lg btn-block mb-3"
href="{% url 'core:tummytime-add' %}?timer={{ timer.id }}"
role="button"><i class="icon icon-tummytime" aria-hidden="true"></i> Tummy Time</a>
role="button"><i class="icon icon-tummytime" aria-hidden="true"></i>
{% trans "Tummy Time" %}
</a>
{% endif %}
<div class="btn-group btn-group-lg center-block" role="group" aria-label="Timer actions">
<div class="btn-group btn-group-lg center-block" role="group" aria-label="{% trans "Timer actions" %}">
{% if perms.core.delete_timer %}
<a class="btn btn-danger"

View File

@ -1,23 +1,23 @@
{% extends 'babybuddy/page.html' %}
{% load duration %}
{% load duration i18n %}
{% block title %}Timer{% endblock %}
{% block title %}{% trans "Timer" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">Timers</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:timer-list' %}">{% trans "Timers" %}</a></li>
{% if object %}
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:timer-detail' object.id %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Start</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Start" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Start Timer</h1>
<h1>{% trans "Start Timer" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,25 +1,25 @@
{% extends 'babybuddy/page.html' %}
{% load bootstrap duration widget_tweaks %}
{% load bootstrap duration i18n widget_tweaks %}
{% block title %}Timers{% endblock %}
{% block title %}{% trans "Timers" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Timers</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Timers" %}</li>
{% endblock %}
{% block content %}
<h1>Timers</h1>
<h1>{% trans "Timers" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Name</th>
<th>Start</th>
<th>Duration</th>
<th>End</th>
<th>Active</th>
<th>User</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Start" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "End" %}</th>
<th>{% trans "Active" %}</th>
<th>{% trans "User" %}</th>
</tr>
</thead>
<tbody>
@ -34,7 +34,7 @@
</tr>
{% empty %}
<tr>
<th colspan="7">No timer entries found.</th>
<th colspan="7">{% trans "No timer entries found." %}</th>
</tr>
{% endfor %}
</tbody>

View File

@ -1,30 +1,34 @@
{% load i18n %}
<li class="nav-item dropdown">
<a id="nav-timer-menu-link"
class="nav-link dropdown-toggle"
href="#"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon icon-timer" aria-hidden="true"></i> Timers</a>
aria-expanded="false"><i class="icon icon-timer" aria-hidden="true"></i>
{% trans "Timers" %}
</a>
<div class="dropdown-menu" aria-labelledby="nav-timer-menu-link">
{% if perms.core.add_timer %}
<a class="dropdown-item" href="{% url 'core:timer-add-quick' %}">
<i class="icon icon-timer" aria-hidden="true"></i> Quick Start Timer
<i class="icon icon-timer" aria-hidden="true"></i> {% trans "Quick Start Timer" %}
</a>
<a class="dropdown-item" href="{% url 'core:timer-add' %}">
<i class="icon icon-add" aria-hidden="true"></i> Start Timer
<i class="icon icon-add" aria-hidden="true"></i> {% trans "Start Timer" %}
</a>
{% endif %}
{% if perms.core.view_timer %}
<a class="dropdown-item" href="{% url 'core:timer-list' %}">
<i class="icon icon-list" aria-hidden="true"></i> View Timers
<i class="icon icon-list" aria-hidden="true"></i> {% trans "View Timers" %}
</a>
{% endif %}
{% if timers %}
<h6 class="dropdown-header">Active Timers</h6>
<h6 class="dropdown-header">{% trans "Active Timers" %}</h6>
{% for timer in timers %}
<a class="dropdown-item" href="{% url 'core:timer-detail' timer.id %}">{{ timer }} ({{ timer.user }})</a>
{% empty %}
<a class="dropdown-item disabled" href="#">None</a>
<a class="dropdown-item disabled" href="#">{% trans "None" %}</a>
{% endfor %}
{% endif %}
</div>

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Tummy Time Entry{% endblock %}
{% block title %}{% trans "Delete a Tummy Time Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:tummytime-list' %}">Tummy Time</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:tummytime-list' %}">{% trans "Tummy Time" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:tummytime-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:tummytime-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if request.resolver_match.url_name == 'tummytime-update' %}
Update a Tummy Time Entry
{% trans "Update a Tummy Time Entry" %}
{% else %}
Add a Tummy Time Entry
{% trans "Add a Tummy Time Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:tummytime-list' %}">Tummy Time</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:tummytime-list' %}">{% trans "Tummy Time" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Tummy Time Entry</h1>
<h1>{% trans "Add a Tummy Time Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,26 +1,25 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load duration %}
{% load duration i18n widget_tweaks %}
{% block title %}Tummy Time{% endblock %}
{% block title %}{% trans "Tummy Time" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Tummy Time</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Tummy Time" %}</li>
{% endblock %}
{% block content %}
<h1>Tummy Time</h1>
<h1>{% trans "Tummy Time" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th>Duration</th>
<th>Start</th>
<th>End</th>
<th>Milestone</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Start" %}</th>
<th>{% trans "End" %}</th>
<th>{% trans "Milestone" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -32,7 +31,7 @@
<td>{{ tummytime.end|date:'n/j/y G:i' }}</td>
<td>{{ tummytime.milestone }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_tummytime %}
<a href="{% url 'core:tummytime-update' tummytime.id %}" class="btn btn-primary">
@ -51,7 +50,7 @@
</tr>
{% empty %}
<tr>
<th colspan="6">No tummy time entries found.</th>
<th colspan="6">{% trans "No tummy time entries found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -61,7 +60,7 @@
{% if perms.core.add_tummytime %}
<a href="{% url 'core:tummytime-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-tummytime" aria-hidden="true"></i> Add a Tummy Time Entry</a>
<i class="icon icon-tummytime" aria-hidden="true"></i> {% trans "Add a Tummy Time Entry" %}</a>
{% endif %}
{% endblock %}

View File

@ -1,18 +1,18 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Delete a Weight Entry{% endblock %}
{% block title %}{% trans "Delete a Weight Entry" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:weight-list' %}">Weight</a></li>
<li class="breadcrumb-item active" aria-current="page">Delete</li>
<li class="breadcrumb-item"><a href="{% url 'core:weight-list' %}">{% trans "Weight" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Delete" %}</li>
{% endblock %}
{% block content %}
<form role="form" method="post">
{% csrf_token %}
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
<input type="submit" value="Delete" class="btn btn-danger" />
<a href="{% url 'core:weight-list' %}" class="btn btn-default">Cancel</a>
{% blocktrans %}<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>{% endblocktrans %}
<input type="submit" value="{% trans "Delete" %}" class="btn btn-danger" />
<a href="{% url 'core:weight-list' %}" class="btn btn-default">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,27 +1,28 @@
{% extends 'babybuddy/page.html' %}
{% load i18n %}
{% block title %}
{% if object %}
{{ object }}
{% else %}
Add a Weight Entry
{% trans "Add a Weight Entry" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:weight-list' %}">Weight</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:weight-list' %}">{% trans "Weight" %}</a></li>
{% if object %}
<li class="breadcrumb-item active" aria-current="page">Update</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">Add a Weight Entry</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Add a Weight Entry" %}</li>
{% endif %}
{% endblock %}
{% block content %}
{% if object %}
<h1>Update <span class="text-info">{{ object }}</span></h1>
{% blocktrans %}<h1>Update <span class="text-info">{{ object }}</span></h1>{% endblocktrans %}
{% else %}
<h1>Add a Weight Entry</h1>
<h1>{% trans "Add a Weight Entry" %}</h1>
{% endif %}
{% include 'babybuddy/form.html' %}
{% endblock %}

View File

@ -1,23 +1,23 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% load i18n widget_tweaks %}
{% block title %}Weight{% endblock %}
{% block title %}{% trans "Weight" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">Weight</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Weight" %}</li>
{% endblock %}
{% block content %}
<h1>Weight</h1>
<h1>{% trans "Weight" %}</h1>
{% include 'babybuddy/filter.html' %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th>Child</th>
<th>Weight</th>
<th>Date</th>
<th class="text-center">Actions</th>
<th>{% trans "Child" %}</th>
<th>{% trans "Weight" %}</th>
<th>{% trans "Date" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -27,7 +27,7 @@
<td>{{ object.weight }}</td>
<td>{{ object.date }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
<div class="btn-group btn-group-sm" role="group" aria-label="{% trans "Actions" %}">
{% if perms.core.change_weight %}
<a href="{% url 'core:weight-update' object.id %}" class="btn btn-primary">
@ -46,7 +46,7 @@
</tr>
{% empty %}
<tr>
<th colspan="4">No weight entries found.</th>
<th colspan="4">{% trans "No weight entries found." %}</th>
</tr>
{% endfor %}
</tbody>
@ -56,7 +56,7 @@
{% if perms.core.add_weight %}
<a href="{% url 'core:weight-add' %}" class="btn btn-sm btn-success">
<i class="icon icon-weight" aria-hidden="true"></i> Add a Weight Entry
<i class="icon icon-weight" aria-hidden="true"></i> {% trans "Add a Weight Entry" %}
</a>
{% endif %}

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils import timezone
from django.utils.translation import gettext as _
from core.models import DiaperChange, Feeding, Sleep, TummyTime
@ -20,7 +21,9 @@ def get_objects(child, date):
for instance in instances:
events.append({
'time': timezone.localtime(instance.time),
'event': '{} had a diaper change.'.format(child.first_name),
'event': _('%(child)s had a diaper change.') % {
'child': child.first_name
},
'model_name': instance.model_name,
})
@ -29,13 +32,17 @@ def get_objects(child, date):
for instance in instances:
events.append({
'time': timezone.localtime(instance.start),
'event': '{} started feeding.'.format(instance.child.first_name),
'event': _('%(child)s started feeding.') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': '{} finished feeding.'.format(instance.child.first_name),
'event': _('%(child)s finished feeding.') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'end'
})
@ -45,13 +52,17 @@ def get_objects(child, date):
for instance in instances:
events.append({
'time': timezone.localtime(instance.start),
'event': '{} fell asleep.'.format(instance.child.first_name),
'event': _('%(child)s fell asleep.') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': '{} woke up.'.format(instance.child.first_name),
'event': _('%(child)s woke up.') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'end'
})
@ -61,15 +72,17 @@ def get_objects(child, date):
for instance in instances:
events.append({
'time': timezone.localtime(instance.start),
'event': '{} started tummy time!'.format(
instance.child.first_name),
'event': _('%(child)s started tummy time!') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': '{} finished tummy time.'.format(
instance.child.first_name),
'event': _('%(child)s finished tummy time.') % {
'child': instance.child.first_name
},
'model_name': instance.model_name,
'type': 'end'
})

View File

@ -3,6 +3,7 @@ from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
@ -16,9 +17,9 @@ class CoreAddView(PermissionRequired403Mixin, SuccessMessageMixin, CreateView):
def get_success_message(self, cleaned_data):
cleaned_data['model'] = self.model._meta.verbose_name.title()
if 'child' in cleaned_data:
self.success_message = '%(model)s entry for %(child)s added!'
self.success_message = _('%(model)s entry for %(child)s added!')
else:
self.success_message = '%(model)s entry added!'
self.success_message = _('%(model)s entry added!')
return self.success_message % cleaned_data
@ -27,9 +28,9 @@ class CoreUpdateView(PermissionRequired403Mixin, SuccessMessageMixin,
def get_success_message(self, cleaned_data):
cleaned_data['model'] = self.model._meta.verbose_name.title()
if 'child' in cleaned_data:
self.success_message = '%(model)s entry for %(child)s updated.'
self.success_message = _('%(model)s entry for %(child)s updated.')
else:
self.success_message = '%(model)s entry updated.'
self.success_message = _('%(model)s entry updated.')
return self.success_message % cleaned_data
@ -58,7 +59,7 @@ class ChildAdd(CoreAddView):
permission_required = ('core.add_child',)
form_class = forms.ChildForm
success_url = reverse_lazy('core:child-list')
success_message = '%(first_name)s %(last_name)s added!'
success_message = _('%(first_name)s %(last_name)s added!')
class ChildDetail(PermissionRequired403Mixin, DetailView):
@ -286,7 +287,7 @@ class TimerRestart(PermissionRequired403Mixin, RedirectView):
class TimerStop(PermissionRequired403Mixin, SuccessMessageMixin, RedirectView):
permission_required = ('core.change_timer',)
success_message = '%(timer)s stopped.'
success_message = _('%(timer)s stopped.')
def get(self, request, *args, **kwargs):
instance = models.Timer.objects.get(id=kwargs['pk'])

View File

@ -1,12 +1,13 @@
{% extends 'cards/base.html' %}
{% load i18n %}
{% block header %}Last Diaper Change{% endblock %}
{% block header %}{% trans "Last Diaper Change" %}{% endblock %}
{% block title %}
{% if change %}
{{ change.time|timesince }} ago
{% blocktrans with time=change.time|timesince %}{{ time }} ago{% endblocktrans %}
{% else %}
Never
{% trans "Never" %}
{% endif %}
{% endblock %}

View File

@ -1,9 +1,10 @@
{% extends 'cards/base.html' %}
{% load i18n %}
{% block header %}Diaper Changes{% endblock %}
{% block header %}{% trans "Diaper Changes" %}{% endblock %}
{% block title %}
Past Week
{% trans "Past Week" %}
{% endblock %}
{% block content %}
@ -14,23 +15,23 @@
{% if info.wet_pct > 0 %}
<div class="progress-bar bg-primary lead"
role="progressbar"
style="width: {{ info.wet_pct }}%;">{{ info.wet|floatformat:'0' }}&nbsp;wet</div>
style="width: {{ info.wet_pct|safe }}%;">{{ info.wet|floatformat:'0' }}&nbsp;{% trans "wet" %}</div>
{% endif %}
{% if info.solid_pct > 0 %}
<div class="progress-bar bg-secondary lead"
role="progressbar"
style="width: {{ info.solid_pct }}%;">
{{ info.solid|floatformat:'0' }}&nbsp;solid</div>
style="width: {{ info.solid_pct|safe }}%;">
{{ info.solid|floatformat:'0' }}&nbsp;{% trans "solid" %}</div>
{% endif %}
</div>
<div class="text-center text-light small">
{% if key == 0 %}
today
{% trans "today" %}
{% elif key == 1 %}
yesterday
{% trans "yesterday" %}
{% else %}
{{ key }} days ago
{% blocktrans %}{{ key }} days ago{% endblocktrans %}
{% endif %}
</div>
{% endif %}

View File

@ -1,12 +1,13 @@
{% extends 'cards/base.html' %}
{% load i18n %}
{% block header %}Last Feeding{% endblock %}
{% block header %}{% trans "Last Feeding" %}{% endblock %}
{% block title %}
{% if feeding %}
{{ feeding.end|timesince }} ago
{% blocktrans with time=feeding.end|timesince %}{{ time }} ago{% endblocktrans %}
{% else %}
Never
{% trans "Never" %}
{% endif %}
{% endblock %}

View File

@ -1,11 +1,12 @@
{% extends 'cards/base.html' %}
{% load i18n %}
{% block header %}Last Feeding Method{% endblock %}
{% block header %}{% trans "Last Feeding Method" %}{% endblock %}
{% block title %}
{% if feeding %}
<div class="display-4 text-center">{{ feeding.method }}</div>
{% else %}
None
{% trans "None" %}
{% endif %}
{% endblock %}

View File

@ -1,18 +1,20 @@
{% extends 'cards/base.html' %}
{% load duration %}
{% load duration i18n %}
{% block header %}Today's Sleep{% endblock %}
{% block header %}{% trans "Today's Sleep" %}{% endblock %}
{% block title %}
{% if total %}
{{ total|duration_string }}
{% else %}
<i class="icon icon-sad" aria-hidden="true"></i>
None yet today
{% trans "None yet today" %}
<i class="icon icon-sad" aria-hidden="true"></i>
{% endif %}
{% endblock %}
{% block content %}
{% if count > 0 %}{{ count }} sleep entries{% endif %}
{% if count > 0 %}
{% blocktrans %}{{ count }} sleep entries{% endblocktrans %}
{% endif %}
{% endblock %}

View File

@ -1,13 +1,13 @@
{% extends 'cards/base.html' %}
{% load duration %}
{% load duration i18n %}
{% block header %}Last Slept{% endblock %}
{% block header %}{% trans "Last Slept" %}{% endblock %}
{% block title %}
{% if sleep %}
{{ sleep.end|timesince }} ago
{% blocktrans with time=sleep.end|timesince %}{{ time }} ago{% endblocktrans %}
{% else %}
Never
{% trans "Never" %}
{% endif %}
{% endblock %}

View File

@ -1,14 +1,14 @@
{% extends 'cards/base.html' %}
{% load duration %}
{% load duration i18n %}
{% block header %}Today's Naps{% endblock %}
{% block header %}{% trans "Today's Naps" %}{% endblock %}
{% block title %}
{% if count %}
{{ count }} nap{{ count|pluralize }}
{% blocktrans with plural=count|pluralize %}{{ count }} nap{{ plural }}{% endblocktrans %}
{% else %}
<i class="icon icon-sad" aria-hidden="true"></i>
None yet today
{% trans "None yet today" %}
<i class="icon icon-sad" aria-hidden="true"></i>
{% endif %}
{% endblock %}

View File

@ -1,9 +1,9 @@
{% load duration %}
{% load duration i18n %}
<div class="card card-dashboard card-statistics">
<div class="card-header">
<i class="icon icon-graph pull-left" aria-hidden="true"></i>
Statistics
{% trans "Statistics" %}
</div>
<div class="card-body text-center">
<div id="statistics-carousel" class="carousel slide" data-interval="false">
@ -20,7 +20,7 @@
{{ stat.stat }}
{% endif %}
{% else %}
<em>Not enough data</em>
<em>{% trans "Not enough data" %}</em>
{% endif %}
</span>
<div class="card-text">{{ stat.title }}</div>
@ -29,11 +29,11 @@
</div>
<a class="carousel-control-prev" href="#statistics-carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
<span class="sr-only">{% trans "Previous" %}</span>
</a>
<a class="carousel-control-next" href="#statistics-carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
<span class="sr-only">{% trans "Next" %}</span>
</a>
</div>
</div>

View File

@ -1,10 +1,11 @@
{% extends 'cards/base.html' %}
{% load i18n %}
{% block header %}Active Timers{% endblock %}
{% block header %}{% trans "Active Timers" %}{% endblock %}
{% block title %}
{% with instances|length as count %}
{{ count }} active timer{{ count|pluralize }}
{% blocktrans with plural=count|pluralize %}{{ count }} active timer{{ plural }}{% endblocktrans %}
{% endwith %}
{% endblock %}
@ -13,7 +14,10 @@
{% for instance in instances %}
<a href="{% url 'core:timer-detail' instance.id %}"
class="list-group-item list-group-item-action">
<strong>{{ instance }}</strong> <p class="text-muted small m-0">Started by {{ instance.user }} at {{ instance.start|time }}</p>
<strong>{{ instance }}</strong>
<p class="text-muted small m-0">
{% blocktrans with start=instance.start|time %}Started by {{ instance.user }} at {{ start }}{% endblocktrans %}
</p>
</a>
{% endfor %}
</ul>

View File

@ -1,14 +1,14 @@
{% extends 'cards/base.html' %}
{% load duration %}
{% load duration i18n %}
{% block header %}Today's Tummy Time{% endblock %}
{% block header %}{% trans "Today's Tummy Time" %}{% endblock %}
{% block title %}
{% if stats.count > 0 %}
{{ stats.total|duration_string }}
{% else %}
<i class="icon icon-sad" aria-hidden="true"></i>
None yet today
{% trans "None yet today" %}
<i class="icon icon-sad" aria-hidden="true"></i>
{% endif %}
{% endblock %}
@ -16,7 +16,9 @@
{% block listgroup %}
<ul class="list-group list-group-flush text-muted small">
{% for instance in instances %}
<li class="list-group-item">{{ instance.duration|duration_string }} at {{ instance.end|time }}</li>
<li class="list-group-item">
{% blocktrans with duration=instance.duration|duration_string end=instance.end|time %}{{ duration }} at {{ end }}{% endblocktrans %}
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,13 +1,13 @@
{% extends 'cards/base.html' %}
{% load duration %}
{% load duration i18n %}
{% block header %}Last Tummy Time{% endblock %}
{% block header %}{% trans "Last Tummy Time" %}{% endblock %}
{% block title %}
{% if tummytime %}
{{ tummytime.time|timesince }} ago
{% blocktrans with time=tummytime.time|timesince %}{{ time }} ago{% endblocktrans %}
{% else %}
Never
{% trans "Never" %}
{% endif %}
{% endblock %}

View File

@ -1,12 +1,12 @@
{% extends 'babybuddy/page.html' %}
{% load cards %}
{% load cards i18n %}
{% block title %}Dashboard - {{ object }}{% endblock %}
{% block title %}{% trans "Dashboard" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">Children</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Dashboard</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Dashboard" %}</li>
{% endblock %}
{% block content %}

View File

@ -1,4 +1,6 @@
<div class="child-actions btn-group btn-group-lg center-block" role="group" aria-label="Child actions">
{% load i18n %}
<div class="child-actions btn-group btn-group-lg center-block" role="group" aria-label="{% trans "Child actions" %}">
{% if perms.core.view_child %}
<a href="{% url 'dashboard:dashboard-child' object.slug %}" class="btn btn-success">
@ -14,12 +16,12 @@
aria-haspopup="true"
aria-expanded="false"><i class="icon icon-graph" aria-hidden="true"></i></button>
<div class="dropdown-menu" aria-labelledby="reports-dropdown">
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-types-child' object.slug %}">Diaper Change Types</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}">Diaper Lifetimes</a>
<a class="dropdown-item" href="{% url 'reports:report-feeding-duration-child' object.slug %}">Feeding Durations (Average)</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-pattern-child' object.slug %}">Sleep Pattern</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">Sleep Totals</a>
<a class="dropdown-item" href="{% url 'reports:report-weight-weight-child' object.slug %}">Weight</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-types-child' object.slug %}">{% trans "Diaper Change Types" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}">{% trans "Diaper Lifetimes" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-feeding-duration-child' object.slug %}">{% trans "Feeding Durations (Average)" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-pattern-child' object.slug %}">{% trans "Sleep Pattern" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">{% trans "Sleep Totals" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-weight-weight-child' object.slug %}">{% trans "Weight" %}</a>
</div>
</div>

View File

@ -1,10 +1,10 @@
{% extends 'babybuddy/page.html' %}
{% load static thumbnail %}
{% load i18n static thumbnail %}
{% block title %}Dashboard{% endblock %}
{% block title %}{% trans "Dashboard" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item font-weight-bold">Dashboard</li>
<li class="breadcrumb-item font-weight-bold">{% trans "Dashboard" %}</li>
{% endblock %}
{% block content %}
@ -24,8 +24,8 @@
<h4 class="card-title">{{ object }}</h4>
<div class="card-text">
<p class="lead">
Born <span class="text-secondary">{{ object.birth_date }}</span><br/>
Age <span class="text-secondary">{{ object.birth_date|timesince }}</span>
{% trans "Born" %} <span class="text-secondary">{{ object.birth_date }}</span><br/>
{% trans "Age" %} <span class="text-secondary">{{ object.birth_date|timesince }}</span>
</p>
{% include 'dashboard/child_button_group.html' %}
</div>

View File

@ -3,6 +3,7 @@ from django import template
from django.db.models import Avg, Count, Sum
from django.db.models.functions import TruncDate
from django.utils import timezone
from django.utils.translation import gettext as _
from core import models
@ -163,39 +164,39 @@ def card_statistics(child):
stats.append({
'type': 'duration',
'stat': changes['btwn_average'],
'title': 'Diaper change frequency'})
'title': _('Diaper change frequency')})
feedings = _feeding_statistics(child)
stats.append({
'type': 'duration',
'stat': feedings['btwn_average'],
'title': 'Feeding frequency'})
'title': _('Feeding frequency')})
naps = _nap_statistics(child)
stats.append({
'type': 'duration',
'stat': naps['average'],
'title': 'Average nap duration'})
'title': _('Average nap duration')})
stats.append({
'type': 'float',
'stat': naps['avg_per_day'],
'title': 'Average naps per day'})
'title': _('Average naps per day')})
sleep = _sleep_statistics(child)
stats.append({
'type': 'duration',
'stat': sleep['average'],
'title': 'Average sleep duration'})
'title': _('Average sleep duration')})
stats.append({
'type': 'duration',
'stat': sleep['btwn_average'],
'title': 'Average awake duration'})
'title': _('Average awake duration')})
weight = _weight_statistics(child)
stats.append({
'type': 'float',
'stat': weight['change_weekly'],
'title': 'Weight change per week'})
'title': _('Weight change per week')})
return {'stats': stats}

View File

@ -171,6 +171,18 @@ gulp.task('makemigrations', function(cb) {
spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
});
gulp.task('makemessages', function(cb) {
var command = ['run', 'python', 'manage.py', 'makemessages'];
command = command.concat(process.argv.splice(3));
spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
});
gulp.task('compilemessages', function(cb) {
var command = ['run', 'python', 'manage.py', 'compilemessages'];
command = command.concat(process.argv.splice(3));
spawn('pipenv', command, { stdio: 'inherit' }).on('exit', cb);
});
gulp.task('reset', function(cb) {
spawn(
'pipenv',

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
@ -22,7 +24,7 @@ def diaperchange_lifetimes(changes):
trace = go.Box(
y=[round(d.seconds/3600, 2) for d in durations],
name='Changes',
name=_('Changes'),
jitter=0.3,
pointpos=-1.8,
boxpoints='all'
@ -30,8 +32,8 @@ def diaperchange_lifetimes(changes):
layout_args = utils.default_graph_layout_options()
layout_args['height'] = 800
layout_args['title'] = '<b>Diaper Lifetimes</b>'
layout_args['yaxis']['title'] = 'Time between changes (hours)'
layout_args['title'] = _('<b>Diaper Lifetimes</b>')
layout_args['yaxis']['title'] = _('Time between changes (hours)')
layout_args['yaxis']['zeroline'] = False
layout_args['yaxis']['dtick'] = 1

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from django.db.models import Count, Case, When
from django.db.models.functions import TruncDate
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
@ -23,28 +24,28 @@ def diaperchange_types(changes):
solid_trace = go.Scatter(
mode='markers',
name='Solid',
name=_('Solid'),
x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('solid_count', flat=True)),
)
wet_trace = go.Scatter(
mode='markers',
name='Wet',
name=_('Wet'),
x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('wet_count', flat=True))
)
total_trace = go.Scatter(
name='Total',
name=_('Total'),
x=list(changes.values_list('date', flat=True)),
y=list(changes.values_list('total', flat=True))
)
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = '<b>Diaper Change Types</b>'
layout_args['xaxis']['title'] = 'Date'
layout_args['title'] = _('<b>Diaper Change Types</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = 'Number of changes'
layout_args['yaxis']['title'] = _('Number of changes')
fig = go.Figure({
'data': [solid_trace, wet_trace, total_trace],

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from django.db.models import Count, Sum
from django.db.models.functions import TruncDate
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
@ -32,7 +33,7 @@ def feeding_duration(instances):
averages.append(total['sum']/total['count'])
trace_avg = go.Scatter(
name='Average duration',
name=_('Average duration'),
line=dict(shape='spline'),
x=list(totals.values_list('date', flat=True)),
y=[td.seconds/60 for td in averages],
@ -40,7 +41,7 @@ def feeding_duration(instances):
text=[_duration_string_ms(td) for td in averages]
)
trace_count = go.Scatter(
name='Total feedings',
name=_('Total feedings'),
mode='markers',
x=list(totals.values_list('date', flat=True)),
y=list(totals.values_list('count', flat=True)),
@ -49,12 +50,12 @@ def feeding_duration(instances):
)
layout_args = utils.default_graph_layout_options()
layout_args['title'] = '<b>Average Feeding Durations</b>'
layout_args['xaxis']['title'] = 'Date'
layout_args['title'] = _('<b>Average Feeding Durations</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = 'Average duration (minutes)'
layout_args['yaxis']['title'] = _('Average duration (minutes)')
layout_args['yaxis2'] = dict(layout_args['yaxis'])
layout_args['yaxis2']['title'] = 'Number of feedings'
layout_args['yaxis2']['title'] = _('Number of feedings')
layout_args['yaxis2']['overlaying'] = 'y'
layout_args['yaxis2']['side'] = 'right'

View File

@ -2,6 +2,7 @@
from collections import OrderedDict
from django.utils import timezone
from django.utils.translation import gettext as _
import pandas as pd
import plotly.offline as plotly
@ -124,10 +125,10 @@ def sleep_pattern(instances):
layout_args['barmode'] = 'stack'
layout_args['hovermode'] = 'closest'
layout_args['title'] = '<b>Sleep Pattern</b>'
layout_args['title'] = _('<b>Sleep Pattern</b>')
layout_args['height'] = 800
layout_args['xaxis']['title'] = 'Date'
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['tickangle'] = -65
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
@ -137,7 +138,7 @@ def sleep_pattern(instances):
for i in range(30, 60*24, 30):
ticks[i] = (start + timezone.timedelta(minutes=i)).strftime('%I:%M %p')
layout_args['yaxis']['title'] = 'Time of day'
layout_args['yaxis']['title'] = _('Time of day')
layout_args['yaxis']['rangemode'] = 'tozero'
layout_args['yaxis']['tickmode'] = 'array'
layout_args['yaxis']['tickvals'] = list(ticks.keys())

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils import timezone
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
@ -36,7 +37,7 @@ def sleep_totals(instances):
totals[start.date()] += instance.duration
trace = go.Bar(
name='Total sleep',
name=_('Total sleep'),
x=list(totals.keys()),
y=[td.seconds/3600 for td in totals.values()],
hoverinfo='text',
@ -46,10 +47,10 @@ def sleep_totals(instances):
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = '<b>Sleep Totals</b>'
layout_args['xaxis']['title'] = 'Date'
layout_args['title'] = _('<b>Sleep Totals</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = 'Hours of sleep'
layout_args['yaxis']['title'] = _('Hours of sleep')
fig = go.Figure({
'data': [trace],

View File

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext as _
import plotly.offline as plotly
import plotly.graph_objs as go
@ -14,7 +16,7 @@ def weight_weight(objects):
objects = objects.order_by('-date')
trace = go.Scatter(
name='Weight',
name=_('Weight'),
x=list(objects.values_list('date', flat=True)),
y=list(objects.values_list('weight', flat=True)),
fill='tozeroy',
@ -22,10 +24,10 @@ def weight_weight(objects):
layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack'
layout_args['title'] = '<b>Weight</b>'
layout_args['xaxis']['title'] = 'Date'
layout_args['title'] = _('<b>Weight</b>')
layout_args['xaxis']['title'] = _('Date')
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date()
layout_args['yaxis']['title'] = 'Weight'
layout_args['yaxis']['title'] = _('Weight')
fig = go.Figure({
'data': [trace],

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Diaper Lifetimes - {{ object }}{% endblock %}
{% block title %}{% trans "Diaper Lifetimes" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Diaper Lifetimes</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Diaper Lifetimes" %}</li>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Diaper Change Types - {{ object }}{% endblock %}
{% block title %}{% trans "Diaper Change Types" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Diaper Types</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Diaper Change Types" %}</li>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Average Feeding Durations - {{ object }}{% endblock %}
{% block title %}{% trans "Average Feeding Durations" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Average Feeding Durations</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Average Feeding Durations" %}</li>
{% endblock %}

View File

@ -1,12 +1,12 @@
{% extends 'babybuddy/page.html' %}
{% load static %}
{% load i18n static %}
{% block title %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">Children</a></li>
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item">Reports</li>
<li class="breadcrumb-item">{% trans "Reports" %}</li>
{% endblock %}
{% block content %}
@ -16,7 +16,7 @@
{% else %}
<div class="jumbotron text-center">
<i class="icon icon-sad" aria-hidden="true"></i>
There is no enough data to generate this report.
{% trans "There is no enough data to generate this report." %}
<i class="icon icon-sad" aria-hidden="true"></i>
</div>
{% endif %}

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Sleep Patterns - {{ object }}{% endblock %}
{% block title %}{% trans "Sleep Pattern" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Sleep Pattern</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Sleep Pattern" %}</li>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Sleep Totals - {{ object }}{% endblock %}
{% block title %}{% trans "Sleep Totals" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Sleep Totals</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Sleep Totals" %}</li>
{% endblock %}

View File

@ -1,8 +1,9 @@
{% extends 'reports/report_base.html' %}
{% load i18n %}
{% block title %}Weight - {{ object }}{% endblock %}
{% block title %}{% trans "Weight" %} - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Weight</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Weight" %}</li>
{% endblock %}